<commit_before>// Copyright 2011 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include <stdlib.h>

#include <vector>

#include "src/v8.h"

#include "src/base/platform/platform.h"
#include "src/utils-inl.h"
#include "test/cctest/cctest.h"

using namespace v8::internal;

TEST(Utils1) {
  CHECK_EQ(-1000000, FastD2I(-1000000.0));
  CHECK_EQ(-1, FastD2I(-1.0));
  CHECK_EQ(0, FastD2I(0.0));
  CHECK_EQ(1, FastD2I(1.0));
  CHECK_EQ(1000000, FastD2I(1000000.0));

  CHECK_EQ(-1000000, FastD2I(-1000000.123));
  CHECK_EQ(-1, FastD2I(-1.234));
  CHECK_EQ(0, FastD2I(0.345));
  CHECK_EQ(1, FastD2I(1.234));
  CHECK_EQ(1000000, FastD2I(1000000.123));
  // Check that >> is implemented as arithmetic shift right.
  // If this is not true, then ArithmeticShiftRight() must be changed,
  // There are also documented right shifts in assembler.cc of
  // int8_t and intptr_t signed integers.
  CHECK_EQ(-2, -8 >> 2);
  CHECK_EQ(-2, static_cast<int8_t>(-8) >> 2);
  CHECK_EQ(-2, static_cast<int>(static_cast<intptr_t>(-8) >> 2));

  CHECK_EQ(-1000000, FastD2IChecked(-1000000.0));
  CHECK_EQ(-1, FastD2IChecked(-1.0));
  CHECK_EQ(0, FastD2IChecked(0.0));
  CHECK_EQ(1, FastD2IChecked(1.0));
  CHECK_EQ(1000000, FastD2IChecked(1000000.0));

  CHECK_EQ(-1000000, FastD2IChecked(-1000000.123));
  CHECK_EQ(-1, FastD2IChecked(-1.234));
  CHECK_EQ(0, FastD2IChecked(0.345));
  CHECK_EQ(1, FastD2IChecked(1.234));
  CHECK_EQ(1000000, FastD2IChecked(1000000.123));

  CHECK_EQ(INT_MAX, FastD2IChecked(1.0e100));
  CHECK_EQ(INT_MIN, FastD2IChecked(-1.0e100));
  CHECK_EQ(INT_MIN, FastD2IChecked(v8::base::OS::nan_value()));
}

TEST(SNPrintF) {
  // Make sure that strings that are truncated because of too small
  // buffers are zero-terminated anyway.
  const char* s = "the quick lazy .... oh forget it!";
  int length = StrLength(s);
  for (int i = 1; i < length * 2; i++) {
    static const char kMarker = static_cast<char>(42);
    Vector<char> buffer = Vector<char>::New(i + 1);
    buffer[i] = kMarker;
    int n = SNPrintF(Vector<char>(buffer.start(), i), "%s", s);
    CHECK(n <= i);
    CHECK(n == length || n == -1);
    CHECK_EQ(0, strncmp(buffer.start(), s, i - 1));
    CHECK_EQ(kMarker, buffer[i]);
    if (i <= length) {
      CHECK_EQ(i - 1, StrLength(buffer.start()));
    } else {
      CHECK_EQ(length, StrLength(buffer.start()));
    }
    buffer.Dispose();
  }
}

static const int kAreaSize = 512;

void TestMemMove(byte* area1,
                 byte* area2,
                 int src_offset,
                 int dest_offset,
                 int length) {
  for (int i = 0; i < kAreaSize; i++) {
    area1[i] = i & 0xFF;
    area2[i] = i & 0xFF;
  }
  MemMove(area1 + dest_offset, area1 + src_offset, length);
  memmove(area2 + dest_offset, area2 + src_offset, length);
  if (memcmp(area1, area2, kAreaSize) != 0) {
    printf("MemMove(): src_offset: %d, dest_offset: %d, length: %d\n",
           src_offset, dest_offset, length);
    for (int i = 0; i < kAreaSize; i++) {
      if (area1[i] == area2[i]) continue;
      printf("diff at offset %d (%p): is %d, should be %d\n", i,
             reinterpret_cast<void*>(area1 + i), area1[i], area2[i]);
    }
    CHECK(false);
  }
}

TEST(MemMove) {
  v8::V8::Initialize();
  byte* area1 = new byte[kAreaSize];
  byte* area2 = new byte[kAreaSize];

  static const int kMinOffset = 32;
  static const int kMaxOffset = 64;
  static const int kMaxLength = 128;
  STATIC_ASSERT(kMaxOffset + kMaxLength < kAreaSize);

  for (int src_offset = kMinOffset; src_offset <= kMaxOffset; src_offset++) {
    for (int dst_offset = kMinOffset; dst_offset <= kMaxOffset; dst_offset++) {
      for (int length = 0; length <= kMaxLength; length++) {
        TestMemMove(area1, area2, src_offset, dst_offset, length);
      }
    }
  }
  delete[] area1;
  delete[] area2;
}

TEST(Collector) {
  Collector<int> collector(8);
  const int kLoops = 5;
  const int kSequentialSize = 1000;
  const int kBlockSize = 7;
  for (int loop = 0; loop < kLoops; loop++) {
    Vector<int> block = collector.AddBlock(7, 0xbadcafe);
    for (int i = 0; i < kSequentialSize; i++) {
      collector.Add(i);
    }
    for (int i = 0; i < kBlockSize - 1; i++) {
      block[i] = i * 7;
    }
  }
  Vector<int> result = collector.ToVector();
  CHECK_EQ(kLoops * (kBlockSize + kSequentialSize), result.length());
  for (int i = 0; i < kLoops; i++) {
    int offset = i * (kSequentialSize + kBlockSize);
    for (int j = 0; j < kBlockSize - 1; j++) {
      CHECK_EQ(j * 7, result[offset + j]);
    }
    CHECK_EQ(0xbadcafe, result[offset + kBlockSize - 1]);
    for (int j = 0; j < kSequentialSize; j++) {
      CHECK_EQ(j, result[offset + kBlockSize + j]);
    }
  }
  result.Dispose();
}

TEST(SequenceCollector) {
  SequenceCollector<int> collector(8);
  const int kLoops = 5000;
  const int kMaxSequenceSize = 13;
  int total_length = 0;
  for (int loop = 0; loop < kLoops; loop++) {
    int seq_length = loop % kMaxSequenceSize;
    collector.StartSequence();
    for (int j = 0; j < seq_length; j++) {
      collector.Add(j);
    }
    Vector<int> sequence = collector.EndSequence();
    for (int j = 0; j < seq_length; j++) {
      CHECK_EQ(j, sequence[j]);
    }
    total_length += seq_length;
  }
  Vector<int> result = collector.ToVector();
  CHECK_EQ(total_length, result.length());
  int offset = 0;
  for (int loop = 0; loop < kLoops; loop++) {
    int seq_length = loop % kMaxSequenceSize;
    for (int j = 0; j < seq_length; j++) {
      CHECK_EQ(j, result[offset]);
      offset++;
    }
  }
  result.Dispose();
}

TEST(SequenceCollectorRegression) {
  SequenceCollector<char> collector(16);
  collector.StartSequence();
  collector.Add('0');
  collector.AddBlock(
      i::Vector<const char>("12345678901234567890123456789012", 32));
  i::Vector<char> seq = collector.EndSequence();
  CHECK_EQ(0, strncmp("0123456789012345678901234567890123",
                      seq.start(), seq.length()));
}

// TODO(svenpanne) Unconditionally test this when our infrastructure is fixed.
#if 0 && !V8_CC_MSVC && !V8_OS_NACL
TEST(CPlusPlus11Features) {
  struct S {
    bool x;
    struct T {
      double y;
      int z[3];
    } t;
  };
  S s{true, {3.1415, {1, 2, 3}}};
  CHECK_EQ(2, s.t.z[1]);

// TODO(svenpanne) Remove the old-skool code when we ship the new C++ headers.
#if 0
  std::vector<int> vec{11, 22, 33, 44};
#else
  std::vector<int> vec;
  vec.push_back(11);
  vec.push_back(22);
  vec.push_back(33);
  vec.push_back(44);
#endif
  vec.push_back(55);
  vec.push_back(66);
  for (auto& i : vec) {
    ++i;
  }
  int j = 12;
  for (auto i : vec) {
    CHECK_EQ(j, i);
    j += 11;
  }
}
#endif
<commit_msg>Re-enable C++11 tests (still not on VS or NaCL).<commit_after>// Copyright 2011 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include <stdlib.h>

#include <vector>

#include "src/v8.h"

#include "src/base/platform/platform.h"
#include "src/utils-inl.h"
#include "test/cctest/cctest.h"

using namespace v8::internal;

TEST(Utils1) {
  CHECK_EQ(-1000000, FastD2I(-1000000.0));
  CHECK_EQ(-1, FastD2I(-1.0));
  CHECK_EQ(0, FastD2I(0.0));
  CHECK_EQ(1, FastD2I(1.0));
  CHECK_EQ(1000000, FastD2I(1000000.0));

  CHECK_EQ(-1000000, FastD2I(-1000000.123));
  CHECK_EQ(-1, FastD2I(-1.234));
  CHECK_EQ(0, FastD2I(0.345));
  CHECK_EQ(1, FastD2I(1.234));
  CHECK_EQ(1000000, FastD2I(1000000.123));
  // Check that >> is implemented as arithmetic shift right.
  // If this is not true, then ArithmeticShiftRight() must be changed,
  // There are also documented right shifts in assembler.cc of
  // int8_t and intptr_t signed integers.
  CHECK_EQ(-2, -8 >> 2);
  CHECK_EQ(-2, static_cast<int8_t>(-8) >> 2);
  CHECK_EQ(-2, static_cast<int>(static_cast<intptr_t>(-8) >> 2));

  CHECK_EQ(-1000000, FastD2IChecked(-1000000.0));
  CHECK_EQ(-1, FastD2IChecked(-1.0));
  CHECK_EQ(0, FastD2IChecked(0.0));
  CHECK_EQ(1, FastD2IChecked(1.0));
  CHECK_EQ(1000000, FastD2IChecked(1000000.0));

  CHECK_EQ(-1000000, FastD2IChecked(-1000000.123));
  CHECK_EQ(-1, FastD2IChecked(-1.234));
  CHECK_EQ(0, FastD2IChecked(0.345));
  CHECK_EQ(1, FastD2IChecked(1.234));
  CHECK_EQ(1000000, FastD2IChecked(1000000.123));

  CHECK_EQ(INT_MAX, FastD2IChecked(1.0e100));
  CHECK_EQ(INT_MIN, FastD2IChecked(-1.0e100));
  CHECK_EQ(INT_MIN, FastD2IChecked(v8::base::OS::nan_value()));
}

TEST(SNPrintF) {
  // Make sure that strings that are truncated because of too small
  // buffers are zero-terminated anyway.
  const char* s = "the quick lazy .... oh forget it!";
  int length = StrLength(s);
  for (int i = 1; i < length * 2; i++) {
    static const char kMarker = static_cast<char>(42);
    Vector<char> buffer = Vector<char>::New(i + 1);
    buffer[i] = kMarker;
    int n = SNPrintF(Vector<char>(buffer.start(), i), "%s", s);
    CHECK(n <= i);
    CHECK(n == length || n == -1);
    CHECK_EQ(0, strncmp(buffer.start(), s, i - 1));
    CHECK_EQ(kMarker, buffer[i]);
    if (i <= length) {
      CHECK_EQ(i - 1, StrLength(buffer.start()));
    } else {
      CHECK_EQ(length, StrLength(buffer.start()));
    }
    buffer.Dispose();
  }
}

static const int kAreaSize = 512;

void TestMemMove(byte* area1,
                 byte* area2,
                 int src_offset,
                 int dest_offset,
                 int length) {
  for (int i = 0; i < kAreaSize; i++) {
    area1[i] = i & 0xFF;
    area2[i] = i & 0xFF;
  }
  MemMove(area1 + dest_offset, area1 + src_offset, length);
  memmove(area2 + dest_offset, area2 + src_offset, length);
  if (memcmp(area1, area2, kAreaSize) != 0) {
    printf("MemMove(): src_offset: %d, dest_offset: %d, length: %d\n",
           src_offset, dest_offset, length);
    for (int i = 0; i < kAreaSize; i++) {
      if (area1[i] == area2[i]) continue;
      printf("diff at offset %d (%p): is %d, should be %d\n", i,
             reinterpret_cast<void*>(area1 + i), area1[i], area2[i]);
    }
    CHECK(false);
  }
}

TEST(MemMove) {
  v8::V8::Initialize();
  byte* area1 = new byte[kAreaSize];
  byte* area2 = new byte[kAreaSize];

  static const int kMinOffset = 32;
  static const int kMaxOffset = 64;
  static const int kMaxLength = 128;
  STATIC_ASSERT(kMaxOffset + kMaxLength < kAreaSize);

  for (int src_offset = kMinOffset; src_offset <= kMaxOffset; src_offset++) {
    for (int dst_offset = kMinOffset; dst_offset <= kMaxOffset; dst_offset++) {
      for (int length = 0; length <= kMaxLength; length++) {
        TestMemMove(area1, area2, src_offset, dst_offset, length);
      }
    }
  }
  delete[] area1;
  delete[] area2;
}

TEST(Collector) {
  Collector<int> collector(8);
  const int kLoops = 5;
  const int kSequentialSize = 1000;
  const int kBlockSize = 7;
  for (int loop = 0; loop < kLoops; loop++) {
    Vector<int> block = collector.AddBlock(7, 0xbadcafe);
    for (int i = 0; i < kSequentialSize; i++) {
      collector.Add(i);
    }
    for (int i = 0; i < kBlockSize - 1; i++) {
      block[i] = i * 7;
    }
  }
  Vector<int> result = collector.ToVector();
  CHECK_EQ(kLoops * (kBlockSize + kSequentialSize), result.length());
  for (int i = 0; i < kLoops; i++) {
    int offset = i * (kSequentialSize + kBlockSize);
    for (int j = 0; j < kBlockSize - 1; j++) {
      CHECK_EQ(j * 7, result[offset + j]);
    }
    CHECK_EQ(0xbadcafe, result[offset + kBlockSize - 1]);
    for (int j = 0; j < kSequentialSize; j++) {
      CHECK_EQ(j, result[offset + kBlockSize + j]);
    }
  }
  result.Dispose();
}

TEST(SequenceCollector) {
  SequenceCollector<int> collector(8);
  const int kLoops = 5000;
  const int kMaxSequenceSize = 13;
  int total_length = 0;
  for (int loop = 0; loop < kLoops; loop++) {
    int seq_length = loop % kMaxSequenceSize;
    collector.StartSequence();
    for (int j = 0; j < seq_length; j++) {
      collector.Add(j);
    }
    Vector<int> sequence = collector.EndSequence();
    for (int j = 0; j < seq_length; j++) {
      CHECK_EQ(j, sequence[j]);
    }
    total_length += seq_length;
  }
  Vector<int> result = collector.ToVector();
  CHECK_EQ(total_length, result.length());
  int offset = 0;
  for (int loop = 0; loop < kLoops; loop++) {
    int seq_length = loop % kMaxSequenceSize;
    for (int j = 0; j < seq_length; j++) {
      CHECK_EQ(j, result[offset]);
      offset++;
    }
  }
  result.Dispose();
}

TEST(SequenceCollectorRegression) {
  SequenceCollector<char> collector(16);
  collector.StartSequence();
  collector.Add('0');
  collector.AddBlock(
      i::Vector<const char>("12345678901234567890123456789012", 32));
  i::Vector<char> seq = collector.EndSequence();
  CHECK_EQ(0, strncmp("0123456789012345678901234567890123",
                      seq.start(), seq.length()));
}

// TODO(svenpanne) Unconditionally test this when our infrastructure is fixed.
#if !V8_CC_MSVC && !V8_OS_NACL
TEST(CPlusPlus11Features) {
  struct S {
    bool x;
    struct T {
      double y;
      int z[3];
    } t;
  };
  S s{true, {3.1415, {1, 2, 3}}};
  CHECK_EQ(2, s.t.z[1]);

// TODO(svenpanne) Remove the old-skool code when we ship the new C++ headers.
#if 0
  std::vector<int> vec{11, 22, 33, 44};
#else
  std::vector<int> vec;
  vec.push_back(11);
  vec.push_back(22);
  vec.push_back(33);
  vec.push_back(44);
#endif
  vec.push_back(55);
  vec.push_back(66);
  for (auto& i : vec) {
    ++i;
  }
  int j = 12;
  for (auto i : vec) {
    CHECK_EQ(j, i);
    j += 11;
  }
}
#endif
<|endoftext|>


<commit_before>/* Copyright 2017 The TensorFlow Authors. All Rights Reserved.

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.
==============================================================================*/

#include "tensorflow/compiler/aot/codegen.h"

#include <string>
#include <vector>

#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
#include "llvm/Support/TargetSelect.h"
#include "tensorflow/compiler/xla/cpu_function_runtime.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/core/framework/tensor_shape.pb.h"
#include "tensorflow/core/framework/types.pb.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/lib/core/status_test_util.h"
#include "tensorflow/core/lib/io/path.h"
#include "tensorflow/core/platform/env.h"
#include "tensorflow/core/platform/test.h"

namespace tensorflow {
namespace tfcompile {
namespace {

using ::xla::cpu_function_runtime::BufferInfo;

void ExpectErrorContains(const Status& status, absl::string_view str) {
  EXPECT_NE(Status::OK(), status);
  EXPECT_TRUE(absl::StrContains(status.error_message(), str))
      << "expected error: " << status.error_message() << " to contain: " << str;
}

TEST(ValidateCppIdent, Simple) {
  TF_EXPECT_OK(ValidateCppIdent("a", ""));
  TF_EXPECT_OK(ValidateCppIdent("abc", ""));
  TF_EXPECT_OK(ValidateCppIdent("_abc", ""));
  TF_EXPECT_OK(ValidateCppIdent("_abc123", ""));
  // Make sure we didn't skip a valid letter or digit
  string ident;
  for (char c = 'a'; c <= 'z'; c++) {
    ident.append(1, c);
  }
  for (char c = 'A'; c <= 'Z'; c++) {
    ident.append(1, c);
  }
  for (char c = '0'; c <= '9'; c++) {
    ident.append(1, c);
  }
  ident += "_";
  TF_EXPECT_OK(ValidateCppIdent(ident, ""));

  ExpectErrorContains(ValidateCppIdent("", ""), "empty identifier");
  ExpectErrorContains(ValidateCppIdent(" ", ""), "illegal leading char");
  ExpectErrorContains(ValidateCppIdent("0", ""), "illegal leading char");
  ExpectErrorContains(ValidateCppIdent(".", ""), "illegal leading char");
  ExpectErrorContains(ValidateCppIdent(":", ""), "illegal leading char");
  ExpectErrorContains(ValidateCppIdent("a.", ""), "illegal char");
  ExpectErrorContains(ValidateCppIdent("a:", ""), "illegal char");
  ExpectErrorContains(ValidateCppIdent("a:", ""), "illegal char");
}

class ParseCppClassTest : public ::testing::Test {
 protected:
  void ExpectOK(const string& cpp_class, const string& want_class_name,
                const std::vector<string>& want_namespaces) {
    string class_name;
    std::vector<string> namespaces;
    TF_EXPECT_OK(ParseCppClass(cpp_class, &class_name, &namespaces));
    EXPECT_EQ(class_name, want_class_name);
    EXPECT_EQ(namespaces, want_namespaces);
  }

  void ExpectFail(const string& cpp_class) {
    string class_name;
    std::vector<string> namespaces;
    EXPECT_NE(ParseCppClass(cpp_class, &class_name, &namespaces), Status::OK())
        << cpp_class;
  }
};

TEST_F(ParseCppClassTest, ParseOK) {
  ExpectOK("MyClass", "MyClass", {});
  ExpectOK("_MyClass", "_MyClass", {});
  ExpectOK("a::MyClass", "MyClass", {"a"});
  ExpectOK("a::foo::MyClass", "MyClass", {"a", "foo"});
  ExpectOK("a::foo::b::MyClass", "MyClass", {"a", "foo", "b"});
  ExpectOK("a::foo::b::bar::MyClass", "MyClass", {"a", "foo", "b", "bar"});
  ExpectOK("foo::MyClass", "MyClass", {"foo"});
  ExpectOK("_foo::MyClass", "MyClass", {"_foo"});
  ExpectOK("_foo::_MyClass", "_MyClass", {"_foo"});
  ExpectOK("::foo::bar::MyClass", "MyClass", {"foo", "bar"});
  ExpectOK("::_foo::MyClass", "MyClass", {"_foo"});
  ExpectOK("::_foo::_MyClass", "_MyClass", {"_foo"});
  // Make sure we didn't skip a valid letter or digit
  string ident;
  for (char c = 'a'; c <= 'z'; c++) {
    ident.append(1, c);
  }
  for (char c = 'A'; c <= 'Z'; c++) {
    ident.append(1, c);
  }
  for (char c = '0'; c <= '9'; c++) {
    ident.append(1, c);
  }
  ident += "_";
  ExpectOK(ident, ident, {});
  ExpectOK(ident + "::" + ident, ident, {ident});
  ExpectOK(ident + "::" + ident + "::" + ident, ident, {ident, ident});
}

TEST_F(ParseCppClassTest, ParseFail) {
  ExpectFail("");
  ExpectFail("::");
  ExpectFail("0");
  ExpectFail("a.b");
  ExpectFail("a:b");
  ExpectFail(":foo::bar");
  ExpectFail("good::.bad");
  ExpectFail("good:::bad");
  ExpectFail("good::bad::");
  ExpectFail("good::::bad");
  ExpectFail("::::bad");
  ExpectFail("good:: bad");
  ExpectFail("good::0bad");
}

static void CompareWithGoldenFile(
    const string& tensorflow_relative_golden_file_name,
    const string& expected_contents) {
  // To update the golden file, flip update_golden to true and run the
  // following:
  // bazel test --test_strategy=local \
  //   third_party/tensorflow/compiler/aot:codegen_test
  const bool update_golden = false;
  const string golden_file_name = io::JoinPath(
      testing::TensorFlowSrcRoot(), tensorflow_relative_golden_file_name);

  if (update_golden) {
    TF_EXPECT_OK(
        WriteStringToFile(Env::Default(), golden_file_name, expected_contents));
  }

  string golden_file_contents;
  TF_ASSERT_OK(ReadFileToString(Env::Default(), golden_file_name,
                                &golden_file_contents));
  EXPECT_EQ(golden_file_contents, expected_contents);
}

TEST(CodegenTest, Golden) {
  // Normally CpuCompiler::CpuCompiler does this, but in this test we've
  // bypassed the Cpu compiler so we have to do this manually.
  llvm::InitializeNativeTarget();
  llvm::InitializeNativeTargetAsmPrinter();
  LLVMInitializeX86Target();
  LLVMInitializeX86TargetMC();

  CodegenOpts opts;
  opts.class_name = "MyClass";
  opts.target_triple = "x86_64-pc-linux";
  opts.namespaces = {"foo", "bar"};
  opts.gen_name_to_index = true;
  opts.gen_program_shape = true;
  tf2xla::Config config;
  tf2xla::Feed* feed = config.add_feed();
  feed->mutable_id()->set_node_name("feed0");
  feed->set_name("myfeed");
  feed = config.add_feed();
  feed->mutable_id()->set_node_name("feed1");
  tf2xla::Fetch* fetch = config.add_fetch();
  fetch->mutable_id()->set_node_name("fetch0");
  fetch->set_name("myfetch");
  tf2xla::Variable* variable = config.add_variable();
  variable->set_node_name("myvar_readonly");
  variable->mutable_shape()->add_dim()->set_size(1);
  variable->set_type(DT_FLOAT);
  variable->set_readonly(true);
  tf2xla::Variable* variable2 = config.add_variable();
  variable2->set_node_name("myvar");
  variable2->mutable_shape()->add_dim()->set_size(1);
  variable2->set_type(DT_FLOAT);
  tf2xla::Variable* variable3 = config.add_variable();
  variable3->set_node_name("my/var");
  variable3->set_name("myvar2");
  variable3->mutable_shape()->add_dim()->set_size(5);
  variable3->set_type(DT_INT32);
  CompileResult compile_result;
  compile_result.aot.reset(new xla::cpu::CpuAotCompilationResult(
      {},
      {BufferInfo::MakeTempBuffer(1),
       BufferInfo::MakeEntryParameter(/*size=*/8, /*param_number=*/0),
       BufferInfo::MakeTempBuffer(2),
       BufferInfo::MakeEntryParameter(/*size=*/96, /*param_number=*/1),
       BufferInfo::MakeTempBuffer(3), BufferInfo::MakeTempBuffer(120)},
      5, {}));
  compile_result.program_shape =
      xla::ShapeUtil::MakeProgramShape(
          {
              xla::ShapeUtil::MakeShape(xla::F32, {1, 2}),
              xla::ShapeUtil::MakeShape(xla::S64, {3, 4}),
              xla::ShapeUtil::MakeShape(xla::F32, {1}),
              xla::ShapeUtil::MakeShape(xla::F32, {1}),
              xla::ShapeUtil::MakeShape(xla::S32, {5}),
          },
          xla::ShapeUtil::MakeTupleShape({
              xla::ShapeUtil::MakeShape(xla::U32, {5, 6}),
              xla::ShapeUtil::MakeShape(xla::F32, {1}),
              xla::ShapeUtil::MakeShape(xla::S32, {5}),
          }))
          .ToProto();
  compile_result.entry_point = "entry_point";
  compile_result.pointer_size = 8;

  MetadataResult metadata_result;
  TF_ASSERT_OK(GenerateMetadata(opts, compile_result, &metadata_result));

  // The other fields in metadata_result are tested as part of the generated
  // header test.

  CompareWithGoldenFile("compiler/aot/codegen_test_o.golden",
                        metadata_result.object_file_data);

  string header;
  TF_ASSERT_OK(
      GenerateHeader(opts, config, compile_result, metadata_result, &header));

  CompareWithGoldenFile("compiler/aot/codegen_test_h.golden", header);
}
}  // namespace
}  // namespace tfcompile
}  // namespace tensorflow
<commit_msg>[XLA:CPU:AOT] Make test pass on non-x86<commit_after>/* Copyright 2017 The TensorFlow Authors. All Rights Reserved.

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.
==============================================================================*/

#include "tensorflow/compiler/aot/codegen.h"

#include <string>
#include <vector>

#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
#include "llvm/Support/TargetSelect.h"
#include "tensorflow/compiler/xla/cpu_function_runtime.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/core/framework/tensor_shape.pb.h"
#include "tensorflow/core/framework/types.pb.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/lib/core/status_test_util.h"
#include "tensorflow/core/lib/io/path.h"
#include "tensorflow/core/platform/env.h"
#include "tensorflow/core/platform/test.h"

namespace tensorflow {
namespace tfcompile {
namespace {

using ::xla::cpu_function_runtime::BufferInfo;

void ExpectErrorContains(const Status& status, absl::string_view str) {
  EXPECT_NE(Status::OK(), status);
  EXPECT_TRUE(absl::StrContains(status.error_message(), str))
      << "expected error: " << status.error_message() << " to contain: " << str;
}

TEST(ValidateCppIdent, Simple) {
  TF_EXPECT_OK(ValidateCppIdent("a", ""));
  TF_EXPECT_OK(ValidateCppIdent("abc", ""));
  TF_EXPECT_OK(ValidateCppIdent("_abc", ""));
  TF_EXPECT_OK(ValidateCppIdent("_abc123", ""));
  // Make sure we didn't skip a valid letter or digit
  string ident;
  for (char c = 'a'; c <= 'z'; c++) {
    ident.append(1, c);
  }
  for (char c = 'A'; c <= 'Z'; c++) {
    ident.append(1, c);
  }
  for (char c = '0'; c <= '9'; c++) {
    ident.append(1, c);
  }
  ident += "_";
  TF_EXPECT_OK(ValidateCppIdent(ident, ""));

  ExpectErrorContains(ValidateCppIdent("", ""), "empty identifier");
  ExpectErrorContains(ValidateCppIdent(" ", ""), "illegal leading char");
  ExpectErrorContains(ValidateCppIdent("0", ""), "illegal leading char");
  ExpectErrorContains(ValidateCppIdent(".", ""), "illegal leading char");
  ExpectErrorContains(ValidateCppIdent(":", ""), "illegal leading char");
  ExpectErrorContains(ValidateCppIdent("a.", ""), "illegal char");
  ExpectErrorContains(ValidateCppIdent("a:", ""), "illegal char");
  ExpectErrorContains(ValidateCppIdent("a:", ""), "illegal char");
}

class ParseCppClassTest : public ::testing::Test {
 protected:
  void ExpectOK(const string& cpp_class, const string& want_class_name,
                const std::vector<string>& want_namespaces) {
    string class_name;
    std::vector<string> namespaces;
    TF_EXPECT_OK(ParseCppClass(cpp_class, &class_name, &namespaces));
    EXPECT_EQ(class_name, want_class_name);
    EXPECT_EQ(namespaces, want_namespaces);
  }

  void ExpectFail(const string& cpp_class) {
    string class_name;
    std::vector<string> namespaces;
    EXPECT_NE(ParseCppClass(cpp_class, &class_name, &namespaces), Status::OK())
        << cpp_class;
  }
};

TEST_F(ParseCppClassTest, ParseOK) {
  ExpectOK("MyClass", "MyClass", {});
  ExpectOK("_MyClass", "_MyClass", {});
  ExpectOK("a::MyClass", "MyClass", {"a"});
  ExpectOK("a::foo::MyClass", "MyClass", {"a", "foo"});
  ExpectOK("a::foo::b::MyClass", "MyClass", {"a", "foo", "b"});
  ExpectOK("a::foo::b::bar::MyClass", "MyClass", {"a", "foo", "b", "bar"});
  ExpectOK("foo::MyClass", "MyClass", {"foo"});
  ExpectOK("_foo::MyClass", "MyClass", {"_foo"});
  ExpectOK("_foo::_MyClass", "_MyClass", {"_foo"});
  ExpectOK("::foo::bar::MyClass", "MyClass", {"foo", "bar"});
  ExpectOK("::_foo::MyClass", "MyClass", {"_foo"});
  ExpectOK("::_foo::_MyClass", "_MyClass", {"_foo"});
  // Make sure we didn't skip a valid letter or digit
  string ident;
  for (char c = 'a'; c <= 'z'; c++) {
    ident.append(1, c);
  }
  for (char c = 'A'; c <= 'Z'; c++) {
    ident.append(1, c);
  }
  for (char c = '0'; c <= '9'; c++) {
    ident.append(1, c);
  }
  ident += "_";
  ExpectOK(ident, ident, {});
  ExpectOK(ident + "::" + ident, ident, {ident});
  ExpectOK(ident + "::" + ident + "::" + ident, ident, {ident, ident});
}

TEST_F(ParseCppClassTest, ParseFail) {
  ExpectFail("");
  ExpectFail("::");
  ExpectFail("0");
  ExpectFail("a.b");
  ExpectFail("a:b");
  ExpectFail(":foo::bar");
  ExpectFail("good::.bad");
  ExpectFail("good:::bad");
  ExpectFail("good::bad::");
  ExpectFail("good::::bad");
  ExpectFail("::::bad");
  ExpectFail("good:: bad");
  ExpectFail("good::0bad");
}

static void CompareWithGoldenFile(
    const string& tensorflow_relative_golden_file_name,
    const string& expected_contents) {
  // To update the golden file, flip update_golden to true and run the
  // following:
  // bazel test --test_strategy=local \
  //   third_party/tensorflow/compiler/aot:codegen_test
  const bool update_golden = false;
  const string golden_file_name = io::JoinPath(
      testing::TensorFlowSrcRoot(), tensorflow_relative_golden_file_name);

  if (update_golden) {
    TF_EXPECT_OK(
        WriteStringToFile(Env::Default(), golden_file_name, expected_contents));
  }

  string golden_file_contents;
  TF_ASSERT_OK(ReadFileToString(Env::Default(), golden_file_name,
                                &golden_file_contents));
  EXPECT_EQ(golden_file_contents, expected_contents);
}

TEST(CodegenTest, Golden) {
  // Normally CpuCompiler::CpuCompiler does this, but in this test we've
  // bypassed the Cpu compiler so we have to do this manually.
  LLVMInitializeX86Target();
  LLVMInitializeX86TargetInfo();
  LLVMInitializeX86TargetMC();
  LLVMInitializeX86AsmPrinter();

  CodegenOpts opts;
  opts.class_name = "MyClass";
  opts.target_triple = "x86_64-pc-linux";
  opts.namespaces = {"foo", "bar"};
  opts.gen_name_to_index = true;
  opts.gen_program_shape = true;
  tf2xla::Config config;
  tf2xla::Feed* feed = config.add_feed();
  feed->mutable_id()->set_node_name("feed0");
  feed->set_name("myfeed");
  feed = config.add_feed();
  feed->mutable_id()->set_node_name("feed1");
  tf2xla::Fetch* fetch = config.add_fetch();
  fetch->mutable_id()->set_node_name("fetch0");
  fetch->set_name("myfetch");
  tf2xla::Variable* variable = config.add_variable();
  variable->set_node_name("myvar_readonly");
  variable->mutable_shape()->add_dim()->set_size(1);
  variable->set_type(DT_FLOAT);
  variable->set_readonly(true);
  tf2xla::Variable* variable2 = config.add_variable();
  variable2->set_node_name("myvar");
  variable2->mutable_shape()->add_dim()->set_size(1);
  variable2->set_type(DT_FLOAT);
  tf2xla::Variable* variable3 = config.add_variable();
  variable3->set_node_name("my/var");
  variable3->set_name("myvar2");
  variable3->mutable_shape()->add_dim()->set_size(5);
  variable3->set_type(DT_INT32);
  CompileResult compile_result;
  compile_result.aot.reset(new xla::cpu::CpuAotCompilationResult(
      {},
      {BufferInfo::MakeTempBuffer(1),
       BufferInfo::MakeEntryParameter(/*size=*/8, /*param_number=*/0),
       BufferInfo::MakeTempBuffer(2),
       BufferInfo::MakeEntryParameter(/*size=*/96, /*param_number=*/1),
       BufferInfo::MakeTempBuffer(3), BufferInfo::MakeTempBuffer(120)},
      5, {}));
  compile_result.program_shape =
      xla::ShapeUtil::MakeProgramShape(
          {
              xla::ShapeUtil::MakeShape(xla::F32, {1, 2}),
              xla::ShapeUtil::MakeShape(xla::S64, {3, 4}),
              xla::ShapeUtil::MakeShape(xla::F32, {1}),
              xla::ShapeUtil::MakeShape(xla::F32, {1}),
              xla::ShapeUtil::MakeShape(xla::S32, {5}),
          },
          xla::ShapeUtil::MakeTupleShape({
              xla::ShapeUtil::MakeShape(xla::U32, {5, 6}),
              xla::ShapeUtil::MakeShape(xla::F32, {1}),
              xla::ShapeUtil::MakeShape(xla::S32, {5}),
          }))
          .ToProto();
  compile_result.entry_point = "entry_point";
  compile_result.pointer_size = 8;

  MetadataResult metadata_result;
  TF_ASSERT_OK(GenerateMetadata(opts, compile_result, &metadata_result));

  // The other fields in metadata_result are tested as part of the generated
  // header test.

  CompareWithGoldenFile("compiler/aot/codegen_test_o.golden",
                        metadata_result.object_file_data);

  string header;
  TF_ASSERT_OK(
      GenerateHeader(opts, config, compile_result, metadata_result, &header));

  CompareWithGoldenFile("compiler/aot/codegen_test_h.golden", header);
}
}  // namespace
}  // namespace tfcompile
}  // namespace tensorflow
<|endoftext|>


<commit_before>#include <bits/stdc++.h>

using namespace std;

// https://en.wikipedia.org/wiki/Dinic%27s_algorithm in O(V^2 * E)

const int maxnodes = 5000;

int nodes = maxnodes, src, dest;
int dist[maxnodes], q[maxnodes], work[maxnodes];

struct Edge {
    int to, rev;
    int f, cap;
};

vector<Edge> g[maxnodes];

// Adds bidirectional edge
void add_edge(int s, int t, int cap) {
    Edge a = {t, g[t].size(), 0, cap};
    Edge b = {s, g[s].size(), 0, cap};
    g[s].emplace_back(a);
    g[t].emplace_back(b);
}

bool dinic_bfs() {
    fill(dist, dist + nodes, -1);
    dist[src] = 0;
    int qt = 0;
    q[qt++] = src;
    for (int qh = 0; qh < qt; qh++) {
        int u = q[qh];
        for (auto &e : g[u]) {
            int v = e.to;
            if (dist[v] < 0 && e.f < e.cap) {
                dist[v] = dist[u] + 1;
                q[qt++] = v;
            }
        }
    }
    return dist[dest] >= 0;
}

int dinic_dfs(int u, int f) {
    if (u == dest)
        return f;
    for (int &i = work[u]; i < g[u].size(); i++) {
        Edge &e = g[u][i];
        if (e.cap <= e.f) continue;
        int v = e.to;
        if (dist[v] == dist[u] + 1) {
            int df = dinic_dfs(v, min(f, e.cap - e.f));
            if (df > 0) {
                e.f += df;
                g[v][e.rev].f -= df;
                return df;
            }
        }
    }
    return 0;
}

int max_flow(int _src, int _dest) {
    src = _src;
    dest = _dest;
    int result = 0;
    while (dinic_bfs()) {
        fill(work, work + nodes, 0);
        while (int delta = dinic_dfs(src, INT_MAX))
            result += delta;
    }
    return result;
}

int main() {
    int capacity[][3] = {{0, 3, 2},
                         {0, 0, 2},
                         {0, 0, 0}};
    int n = 3;
    nodes = n;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            if (capacity[i][j] != 0)
                add_edge(i, j, capacity[i][j]);

    cout << (4 == max_flow(0, 2)) << endl;
}
<commit_msg>update<commit_after>#include <bits/stdc++.h>

using namespace std;

// https://en.wikipedia.org/wiki/Dinic%27s_algorithm in O(V^2 * E)

const int maxnodes = 5000;

int nodes = maxnodes, src, dest;
int dist[maxnodes], q[maxnodes], work[maxnodes];

struct Edge {
    int to, rev;
    int f, cap;
};

vector<Edge> g[maxnodes];

// Adds bidirectional edge
void add_edge(int s, int t, int cap) {
    Edge a = {t, (int) g[t].size(), 0, cap};
    Edge b = {s, (int) g[s].size(), 0, cap};
    g[s].emplace_back(a);
    g[t].emplace_back(b);
}

bool dinic_bfs() {
    fill(dist, dist + nodes, -1);
    dist[src] = 0;
    int qt = 0;
    q[qt++] = src;
    for (int qh = 0; qh < qt; qh++) {
        int u = q[qh];
        for (auto &e : g[u]) {
            int v = e.to;
            if (dist[v] < 0 && e.f < e.cap) {
                dist[v] = dist[u] + 1;
                q[qt++] = v;
            }
        }
    }
    return dist[dest] >= 0;
}

int dinic_dfs(int u, int f) {
    if (u == dest)
        return f;
    for (int &i = work[u]; i < g[u].size(); i++) {
        Edge &e = g[u][i];
        if (e.cap <= e.f) continue;
        int v = e.to;
        if (dist[v] == dist[u] + 1) {
            int df = dinic_dfs(v, min(f, e.cap - e.f));
            if (df > 0) {
                e.f += df;
                g[v][e.rev].f -= df;
                return df;
            }
        }
    }
    return 0;
}

int max_flow(int _src, int _dest) {
    src = _src;
    dest = _dest;
    int result = 0;
    while (dinic_bfs()) {
        fill(work, work + nodes, 0);
        while (int delta = dinic_dfs(src, INT_MAX))
            result += delta;
    }
    return result;
}

int main() {
    int capacity[][3] = {{0, 3, 2},
                         {0, 0, 2},
                         {0, 0, 0}};
    int n = 3;
    nodes = n;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            if (capacity[i][j] != 0)
                add_edge(i, j, capacity[i][j]);

    cout << (4 == max_flow(0, 2)) << endl;
}
<|endoftext|>


<commit_before>#include <toppra/algorithm/toppra.hpp>

#include <memory>
#include <toppra/algorithm.hpp>
#include <toppra/toppra.hpp>

namespace toppra {
namespace algorithm {

TOPPRA::TOPPRA(LinearConstraintPtrs constraints, const GeometricPathPtr &path)
    : PathParametrizationAlgorithm{std::move(constraints), path} {
  m_solver = Solver::createDefault();
}

ReturnCode TOPPRA::computeForwardPass(value_type vel_start) {
  TOPPRA_LOG_DEBUG("computeForwardPass");
  ReturnCode ret = ReturnCode::OK;
  bool solver_ret;
  Vector g_upper{2}, solution;
  Matrix H;
  auto deltas = m_solver->deltas();
  Bound x, x_next;
  m_data.parametrization(0) = vel_start;
  for (std::size_t i = 0; i < m_N; i++) {
    g_upper << -2 * deltas(i), -1;
    x.setConstant(m_data.parametrization(i));
    x_next = m_data.controllable_sets.row(i + 1);
    solver_ret = m_solver->solveStagewiseOptim(i, H, g_upper, x, x_next, solution);
    if (!solver_ret) {
      ret = ReturnCode::ERR_FAIL_FORWARD_PASS;
      TOPPRA_LOG_DEBUG("Fail: forward pass, idx: " << i);
      break;
    }
    /// \todo This can be optimized further by solving a 1D problem instead of 2D
    m_data.parametrization(i + 1) =
        m_data.parametrization(i) + 2 * deltas(i) * solution(0);
  }

  return ret;
};

}  // namespace algorithm
}  // namespace toppra
<commit_msg>[cpp] Fix numerical issue (tiny negative sd^2)<commit_after>#include <toppra/algorithm/toppra.hpp>

#include <memory>
#include <toppra/algorithm.hpp>
#include <toppra/toppra.hpp>

namespace toppra {
namespace algorithm {

TOPPRA::TOPPRA(LinearConstraintPtrs constraints, const GeometricPathPtr &path)
    : PathParametrizationAlgorithm{std::move(constraints), path} {
  m_solver = Solver::createDefault();
}

ReturnCode TOPPRA::computeForwardPass(value_type vel_start) {
  TOPPRA_LOG_DEBUG("computeForwardPass");
  ReturnCode ret = ReturnCode::OK;
  bool solver_ret;
  Vector g_upper{2}, solution;
  Matrix H;
  auto deltas = m_solver->deltas();
  Bound x, x_next;
  m_data.parametrization(0) = vel_start;
  for (std::size_t i = 0; i < m_N; i++) {
    g_upper << -2 * deltas(i), -1;
    x.setConstant(m_data.parametrization(i));
    x_next = m_data.controllable_sets.row(i + 1);
    solver_ret = m_solver->solveStagewiseOptim(i, H, g_upper, x, x_next, solution);
    if (!solver_ret) {
      ret = ReturnCode::ERR_FAIL_FORWARD_PASS;
      TOPPRA_LOG_DEBUG("Fail: forward pass, idx: " << i);
      break;
    }
    /// \todo This can be optimized further by solving a 1D problem instead of 2D
    m_data.parametrization(i + 1) =
      std::max(0., m_data.parametrization(i) + 2 * deltas(i) * solution(0));
  }

  return ret;
};

}  // namespace algorithm
}  // namespace toppra
<|endoftext|>


<commit_before>/* <x0/plugins/proxy.cpp>
 *
 * This file is part of the x0 web server project and is released under LGPL-3.
 * http://www.xzero.io/
 *
 * (c) 2009-2010 Christian Parpart <trapni@gentoo.org>
 */

#include <x0/http/HttpPlugin.h>
#include <x0/http/HttpServer.h>
#include <x0/http/HttpRequest.h>
#include <x0/io/BufferSource.h>
#include <x0/io/BufferRefSource.h>
#include <x0/SocketSpec.h>
#include <x0/strutils.h>
#include <x0/Url.h>
#include <x0/Types.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>

/* {{{ -- configuration proposal:
 *
 * handler setup {
 * }
 *
 * handler main {
 *     proxy.reverse 'http://127.0.0.1:3000';
 * }
 *
 * --------------------------------------------------------------------------
 * possible tweaks:
 *  - bufsize (0 = unbuffered)
 *  - timeout.connect
 *  - timeout.write
 *  - timeout.read
 *  - ignore_clientabort
 * };
 *
 *
 */ // }}}

#if 0
#	define TRACE(msg...) DEBUG("proxy: " msg)
#else
#	define TRACE(msg...) /*!*/
#endif

// {{{ ProxyConnection API
class ProxyConnection :
	public x0::HttpMessageProcessor
{
private:
	int refCount_;

	x0::HttpRequest *request_;		//!< client's request
	x0::Socket* backend_;			//!< connection to backend app
	bool cloak_;					//!< to cloak or not to cloak the "Server" response header

	int connectTimeout_;
	int readTimeout_;
	int writeTimeout_;

	x0::Buffer writeBuffer_;
	size_t writeOffset_;
	size_t writeProgress_;

	x0::Buffer readBuffer_;
	bool processingDone_;

private:
	void ref();
	void unref();
	inline void close();
	inline void readSome();
	inline void writeSome();

	void onConnected(x0::Socket* s, int revents);
	void io(x0::Socket* s, int revents);
	void onRequestChunk(const x0::BufferRef& chunk);

	static void onAbort(void *p);
	void onWriteComplete();

	// response (HttpMessageProcessor)
	virtual bool onMessageBegin(int versionMajor, int versionMinor, int code, const x0::BufferRef& text);
	virtual bool onMessageHeader(const x0::BufferRef& name, const x0::BufferRef& value);
	virtual bool onMessageContent(const x0::BufferRef& chunk);
	virtual bool onMessageEnd();

public:
	inline ProxyConnection();
	~ProxyConnection();

	void start(x0::HttpRequest* in, x0::Socket* backend, bool cloak);
};
// }}}

// {{{ ProxyConnection impl
ProxyConnection::ProxyConnection() :
	x0::HttpMessageProcessor(x0::HttpMessageProcessor::RESPONSE),
	refCount_(1),
	request_(nullptr),
	backend_(nullptr),
	cloak_(false),

	connectTimeout_(0),
	readTimeout_(0),
	writeTimeout_(0),
	writeBuffer_(),
	writeOffset_(0),
	writeProgress_(0),
	readBuffer_(),
	processingDone_(false)
{
	TRACE("ProxyConnection()");
}

ProxyConnection::~ProxyConnection()
{
	TRACE("~ProxyConnection()");

	if (backend_) {
		backend_->close();

		delete backend_;
	}

	if (request_) {
		if (request_->status == x0::HttpError::Undefined)
			request_->status = x0::HttpError::ServiceUnavailable;

		request_->finish();
	}
}

void ProxyConnection::ref()
{
	++refCount_;
}

void ProxyConnection::unref()
{
	assert(refCount_ > 0);

	--refCount_;

	if (refCount_ == 0) {
		delete this;
	}
}

void ProxyConnection::close()
{
	if (backend_)
		// stop watching on any backend I/O events, if active
		backend_->close();

	unref(); // the one from the constructor
}

void ProxyConnection::onAbort(void *p)
{
	ProxyConnection *self = reinterpret_cast<ProxyConnection *>(p);
	self->close();
}

void ProxyConnection::start(x0::HttpRequest* in, x0::Socket* backend, bool cloak)
{
	TRACE("ProxyConnection.start(in, backend, cloak=%d)", cloak);

	request_ = in;
	request_->setAbortHandler(&ProxyConnection::onAbort, this);
	backend_ = backend;
	cloak_ = cloak;

	// request line
	writeBuffer_.push_back(request_->method);
	writeBuffer_.push_back(' ');
	writeBuffer_.push_back(request_->uri);
	writeBuffer_.push_back(" HTTP/1.1\r\n");

	// request headers
	for (auto& header: request_->requestHeaders) {
		if (iequals(header.name, "Content-Transfer")
				|| iequals(header.name, "Expect")
				|| iequals(header.name, "Connection")
			//	|| iequals(header.name, "X-Forwarded-For")
			//	|| iequals(header.name, "X-Forwarded-Proto")
			) {
			TRACE("skip requestHeader(%s: %s)", header.name.str().c_str(), header.value.str().c_str());
			continue;
		}

		TRACE("pass requestHeader(%s: %s)", header.name.str().c_str(), header.value.str().c_str());
		writeBuffer_.push_back(header.name);
		writeBuffer_.push_back(": ");
		writeBuffer_.push_back(header.value);
		writeBuffer_.push_back("\r\n");
	}

	// additional headers to add
	writeBuffer_.push_back("Connection: closed\r\n");

	writeBuffer_.push_back("X-Forwarded-For: ");
	writeBuffer_.push_back(request_->connection.remoteIP());
	writeBuffer_.push_back("\r\n");

#if defined(WITH_SSL)
	if (!request_->requestHeader("X-Forwarded-Proto").empty()) {
		if (request_->connection.isSecure())
			writeBuffer_.push_back("X-Forwarded-Proto: https\r\n");
		else
			writeBuffer_.push_back("X-Forwarded-Proto: http\r\n");
	}
#endif

	// request headers terminator
	writeBuffer_.push_back("\r\n");

	if (request_->contentAvailable()) {
		TRACE("start: request content available: reading.");
		request_->setBodyCallback<ProxyConnection, &ProxyConnection::onRequestChunk>(this);
	}

	if (backend_->state() == x0::Socket::Connecting) {
		TRACE("start: connect in progress");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::onConnected>(this);
	} else { // connected
		TRACE("start: flushing");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::io>(this);
		backend_->setMode(x0::Socket::ReadWrite);
	}
}

void ProxyConnection::onConnected(x0::Socket* s, int revents)
{
	TRACE("onConnected: content? %d", request_->contentAvailable());
	//TRACE("onConnected.pending:\n%s\n", writeBuffer_.c_str());

	if (backend_->state() == x0::Socket::Operational) {
		TRACE("onConnected: flushing");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::io>(this);
		backend_->setMode(x0::Socket::ReadWrite); // flush already serialized request
	} else {
		close();
	}
}

/** transferres a request body chunk to the origin server.  */
void ProxyConnection::onRequestChunk(const x0::BufferRef& chunk)
{
	TRACE("onRequestChunk(nb:%ld)", chunk.size());
	writeBuffer_.push_back(chunk);

	if (backend_->state() == x0::Socket::Operational) {
		backend_->setMode(x0::Socket::ReadWrite);
	}
}

inline bool validateResponseHeader(const x0::BufferRef& name)
{
	// XXX do not allow origin's connection-level response headers to be passed to the client.
	if (iequals(name, "Connection"))
		return false;

	if (iequals(name, "Transfer-Encoding"))
		return false;

	return true;
}

/** callback, invoked when the origin server has passed us the response status line.
 *
 * We will use the status code only.
 * However, we could pass the text field, too - once x0 core supports it.
 */
bool ProxyConnection::onMessageBegin(int major, int minor, int code, const x0::BufferRef& text)
{
	TRACE("ProxyConnection(%p).status(HTTP/%d.%d, %d, '%s')", (void*)this, major, minor, code, text.str().c_str());

	request_->status = static_cast<x0::HttpError>(code);
	TRACE("status: %d", (int)request_->status);
	return true;
}

/** callback, invoked on every successfully parsed response header.
 *
 * We will pass this header directly to the client's response,
 * if that is NOT a connection-level header.
 */
bool ProxyConnection::onMessageHeader(const x0::BufferRef& name, const x0::BufferRef& value)
{
	TRACE("ProxyConnection(%p).onHeader('%s', '%s')", (void*)this, name.str().c_str(), value.str().c_str());

	if (!validateResponseHeader(name)) {
		TRACE("skipping connection-level header");
		goto done;
	}

	if (cloak_ && iequals(name, "Server")) {
		TRACE("skipping \"Server\"-header");
		goto done;
	}

	request_->responseHeaders.push_back(name.str(), value.str());

done:
	return true;
}

/** callback, invoked on a new response content chunk. */
bool ProxyConnection::onMessageContent(const x0::BufferRef& chunk)
{
	TRACE("messageContent(nb:%lu) state:%s", chunk.size(), backend_->state_str());

	// stop watching for more input
	backend_->setMode(x0::Socket::None);

	// transfer response-body chunk to client
	request_->write<x0::BufferRefSource>(chunk);

	// start listening on backend I/O when chunk has been fully transmitted
	ref();
	request_->writeCallback<ProxyConnection, &ProxyConnection::onWriteComplete>(this);

	return true;
}

void ProxyConnection::onWriteComplete()
{
	TRACE("chunk write complete: %s", state_str());
	backend_->setMode(x0::Socket::Read);
	unref();
}

bool ProxyConnection::onMessageEnd()
{
	TRACE("messageEnd() backend-state:%s", backend_->state_str());
	processingDone_ = true;
	return false;
}

void ProxyConnection::io(x0::Socket* s, int revents)
{
	TRACE("io(0x%04x)", revents);

	if (revents & x0::Socket::Read)
		readSome();

	if (revents & x0::Socket::Write)
		writeSome();
}

void ProxyConnection::writeSome()
{
	TRACE("writeSome() - %s (%d)", state_str(), request_->contentAvailable());

	ssize_t rv = backend_->write(writeBuffer_.data() + writeOffset_, writeBuffer_.size() - writeOffset_);

	if (rv > 0) {
		TRACE("write request: %ld (of %ld) bytes", rv, writeBuffer_.size() - writeOffset_);

		writeOffset_ += rv;
		writeProgress_ += rv;

		if (writeOffset_ == writeBuffer_.size()) {
			TRACE("writeOffset == writeBuffser.size (%ld) p:%ld, ca: %d, clr:%ld", writeOffset_,
				writeProgress_, request_->contentAvailable(), request_->connection.contentLength());

			writeOffset_ = 0;
			writeBuffer_.clear();
			backend_->setMode(x0::Socket::Read);
		}
	} else {
		TRACE("write request failed(%ld): %s", rv, strerror(errno));
		close();
	}
}

void ProxyConnection::readSome()
{
	TRACE("readSome() - %s", state_str());

	std::size_t lower_bound = readBuffer_.size();

	if (lower_bound == readBuffer_.capacity())
		readBuffer_.setCapacity(lower_bound + 4096);

	ssize_t rv = backend_->read(readBuffer_);

	if (rv > 0) {
		TRACE("read response: %ld bytes", rv);
		std::size_t np = process(readBuffer_.ref(lower_bound, rv));
		(void) np;
		TRACE("readSome(): process(): %ld / %ld", np, rv);

		if (processingDone_ || state() == SYNTAX_ERROR) {
			close();
		} else {
			TRACE("resume with io:%d, state:%s", backend_->mode(), backend_->state_str());
			backend_->setMode(x0::Socket::Read);
		}
	} else if (rv == 0) {
		TRACE("http server connection closed");
		close();
	} else {
		TRACE("read response failed(%ld): %s", rv, strerror(errno));

		switch (errno) {
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
		case EWOULDBLOCK:
#endif
		case EAGAIN:
		case EINTR:
			backend_->setMode(x0::Socket::Read);
			break;
		default:
			close();
			break;
		}
	}
}

// }}}

// {{{ plugin class
/**
 * \ingroup plugins
 * \brief proxy content generator plugin
 */
class proxy_plugin :
	public x0::HttpPlugin
{
private:
	bool cloak_;

public:
	proxy_plugin(x0::HttpServer& srv, const std::string& name) :
		x0::HttpPlugin(srv, name),
		cloak_(true)
	{
		registerHandler<proxy_plugin, &proxy_plugin::proxy_reverse>("proxy.reverse");
		registerSetupProperty<proxy_plugin, &proxy_plugin::proxy_cloak>("proxy.cloak", x0::FlowValue::BOOLEAN);
	}

	~proxy_plugin()
	{
	}

private:
	void proxy_cloak(const x0::FlowParams& args, x0::FlowValue& result)
	{
		if (args.size() && (args[0].isBool() || args[0].isNumber())) {
			cloak_ = args[0].toBool();
			TRACE("proxy cloak: %s", cloak_ ? "true" : "false");
		}

		result.set(cloak_);
	}

	bool proxy_reverse(x0::HttpRequest *in, const x0::FlowParams& args)
	{
		// TODO: reuse already spawned proxy connections instead of recreating each time.
		x0::SocketSpec spec;
		spec << args;
		if (!spec.isValid() || spec.backlog >= 0) {
			in->log(x0::Severity::error, "Invalid socket spec passed.");
			return false;
		}

		x0::Socket* backend = new x0::Socket(in->connection.worker().loop());
		backend->open(spec, O_NONBLOCK | O_CLOEXEC);

		if (backend->isOpen()) {
			TRACE("in.content? %d", in->contentAvailable());
			if (ProxyConnection* pc = new ProxyConnection()) {
				pc->start(in, backend, cloak_);
				return true;
			}
		}

		in->status = x0::HttpError::ServiceUnavailable;
		in->finish();

		return true;
	}
};
// }}}

X0_EXPORT_PLUGIN(proxy)
<commit_msg>[plugins] ssl: fixes negated condition wrt X-Forwarded-Proto<commit_after>/* <x0/plugins/proxy.cpp>
 *
 * This file is part of the x0 web server project and is released under LGPL-3.
 * http://www.xzero.io/
 *
 * (c) 2009-2010 Christian Parpart <trapni@gentoo.org>
 */

#include <x0/http/HttpPlugin.h>
#include <x0/http/HttpServer.h>
#include <x0/http/HttpRequest.h>
#include <x0/io/BufferSource.h>
#include <x0/io/BufferRefSource.h>
#include <x0/SocketSpec.h>
#include <x0/strutils.h>
#include <x0/Url.h>
#include <x0/Types.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>

/* {{{ -- configuration proposal:
 *
 * handler setup {
 * }
 *
 * handler main {
 *     proxy.reverse 'http://127.0.0.1:3000';
 * }
 *
 * --------------------------------------------------------------------------
 * possible tweaks:
 *  - bufsize (0 = unbuffered)
 *  - timeout.connect
 *  - timeout.write
 *  - timeout.read
 *  - ignore_clientabort
 * };
 *
 *
 */ // }}}

#if 0
#	define TRACE(msg...) DEBUG("proxy: " msg)
#else
#	define TRACE(msg...) /*!*/
#endif

// {{{ ProxyConnection API
class ProxyConnection :
	public x0::HttpMessageProcessor
{
private:
	int refCount_;

	x0::HttpRequest *request_;		//!< client's request
	x0::Socket* backend_;			//!< connection to backend app
	bool cloak_;					//!< to cloak or not to cloak the "Server" response header

	int connectTimeout_;
	int readTimeout_;
	int writeTimeout_;

	x0::Buffer writeBuffer_;
	size_t writeOffset_;
	size_t writeProgress_;

	x0::Buffer readBuffer_;
	bool processingDone_;

private:
	void ref();
	void unref();
	inline void close();
	inline void readSome();
	inline void writeSome();

	void onConnected(x0::Socket* s, int revents);
	void io(x0::Socket* s, int revents);
	void onRequestChunk(const x0::BufferRef& chunk);

	static void onAbort(void *p);
	void onWriteComplete();

	// response (HttpMessageProcessor)
	virtual bool onMessageBegin(int versionMajor, int versionMinor, int code, const x0::BufferRef& text);
	virtual bool onMessageHeader(const x0::BufferRef& name, const x0::BufferRef& value);
	virtual bool onMessageContent(const x0::BufferRef& chunk);
	virtual bool onMessageEnd();

public:
	inline ProxyConnection();
	~ProxyConnection();

	void start(x0::HttpRequest* in, x0::Socket* backend, bool cloak);
};
// }}}

// {{{ ProxyConnection impl
ProxyConnection::ProxyConnection() :
	x0::HttpMessageProcessor(x0::HttpMessageProcessor::RESPONSE),
	refCount_(1),
	request_(nullptr),
	backend_(nullptr),
	cloak_(false),

	connectTimeout_(0),
	readTimeout_(0),
	writeTimeout_(0),
	writeBuffer_(),
	writeOffset_(0),
	writeProgress_(0),
	readBuffer_(),
	processingDone_(false)
{
	TRACE("ProxyConnection()");
}

ProxyConnection::~ProxyConnection()
{
	TRACE("~ProxyConnection()");

	if (backend_) {
		backend_->close();

		delete backend_;
	}

	if (request_) {
		if (request_->status == x0::HttpError::Undefined)
			request_->status = x0::HttpError::ServiceUnavailable;

		request_->finish();
	}
}

void ProxyConnection::ref()
{
	++refCount_;
}

void ProxyConnection::unref()
{
	assert(refCount_ > 0);

	--refCount_;

	if (refCount_ == 0) {
		delete this;
	}
}

void ProxyConnection::close()
{
	if (backend_)
		// stop watching on any backend I/O events, if active
		backend_->close();

	unref(); // the one from the constructor
}

void ProxyConnection::onAbort(void *p)
{
	ProxyConnection *self = reinterpret_cast<ProxyConnection *>(p);
	self->close();
}

void ProxyConnection::start(x0::HttpRequest* in, x0::Socket* backend, bool cloak)
{
	TRACE("ProxyConnection.start(in, backend, cloak=%d)", cloak);

	request_ = in;
	request_->setAbortHandler(&ProxyConnection::onAbort, this);
	backend_ = backend;
	cloak_ = cloak;

	// request line
	writeBuffer_.push_back(request_->method);
	writeBuffer_.push_back(' ');
	writeBuffer_.push_back(request_->uri);
	writeBuffer_.push_back(" HTTP/1.1\r\n");

	// request headers
	for (auto& header: request_->requestHeaders) {
		if (iequals(header.name, "Content-Transfer")
				|| iequals(header.name, "Expect")
				|| iequals(header.name, "Connection")
			//	|| iequals(header.name, "X-Forwarded-For")
			//	|| iequals(header.name, "X-Forwarded-Proto")
			) {
			TRACE("skip requestHeader(%s: %s)", header.name.str().c_str(), header.value.str().c_str());
			continue;
		}

		TRACE("pass requestHeader(%s: %s)", header.name.str().c_str(), header.value.str().c_str());
		writeBuffer_.push_back(header.name);
		writeBuffer_.push_back(": ");
		writeBuffer_.push_back(header.value);
		writeBuffer_.push_back("\r\n");
	}

	// additional headers to add
	writeBuffer_.push_back("Connection: closed\r\n");

	writeBuffer_.push_back("X-Forwarded-For: ");
	writeBuffer_.push_back(request_->connection.remoteIP());
	writeBuffer_.push_back("\r\n");

#if defined(WITH_SSL)
	if (request_->requestHeader("X-Forwarded-Proto").empty()) {
		if (request_->connection.isSecure())
			writeBuffer_.push_back("X-Forwarded-Proto: https\r\n");
		else
			writeBuffer_.push_back("X-Forwarded-Proto: http\r\n");
	}
#endif

	// request headers terminator
	writeBuffer_.push_back("\r\n");

	if (request_->contentAvailable()) {
		TRACE("start: request content available: reading.");
		request_->setBodyCallback<ProxyConnection, &ProxyConnection::onRequestChunk>(this);
	}

	if (backend_->state() == x0::Socket::Connecting) {
		TRACE("start: connect in progress");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::onConnected>(this);
	} else { // connected
		TRACE("start: flushing");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::io>(this);
		backend_->setMode(x0::Socket::ReadWrite);
	}
}

void ProxyConnection::onConnected(x0::Socket* s, int revents)
{
	TRACE("onConnected: content? %d", request_->contentAvailable());
	//TRACE("onConnected.pending:\n%s\n", writeBuffer_.c_str());

	if (backend_->state() == x0::Socket::Operational) {
		TRACE("onConnected: flushing");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::io>(this);
		backend_->setMode(x0::Socket::ReadWrite); // flush already serialized request
	} else {
		close();
	}
}

/** transferres a request body chunk to the origin server.  */
void ProxyConnection::onRequestChunk(const x0::BufferRef& chunk)
{
	TRACE("onRequestChunk(nb:%ld)", chunk.size());
	writeBuffer_.push_back(chunk);

	if (backend_->state() == x0::Socket::Operational) {
		backend_->setMode(x0::Socket::ReadWrite);
	}
}

inline bool validateResponseHeader(const x0::BufferRef& name)
{
	// XXX do not allow origin's connection-level response headers to be passed to the client.
	if (iequals(name, "Connection"))
		return false;

	if (iequals(name, "Transfer-Encoding"))
		return false;

	return true;
}

/** callback, invoked when the origin server has passed us the response status line.
 *
 * We will use the status code only.
 * However, we could pass the text field, too - once x0 core supports it.
 */
bool ProxyConnection::onMessageBegin(int major, int minor, int code, const x0::BufferRef& text)
{
	TRACE("ProxyConnection(%p).status(HTTP/%d.%d, %d, '%s')", (void*)this, major, minor, code, text.str().c_str());

	request_->status = static_cast<x0::HttpError>(code);
	TRACE("status: %d", (int)request_->status);
	return true;
}

/** callback, invoked on every successfully parsed response header.
 *
 * We will pass this header directly to the client's response,
 * if that is NOT a connection-level header.
 */
bool ProxyConnection::onMessageHeader(const x0::BufferRef& name, const x0::BufferRef& value)
{
	TRACE("ProxyConnection(%p).onHeader('%s', '%s')", (void*)this, name.str().c_str(), value.str().c_str());

	if (!validateResponseHeader(name)) {
		TRACE("skipping connection-level header");
		goto done;
	}

	if (cloak_ && iequals(name, "Server")) {
		TRACE("skipping \"Server\"-header");
		goto done;
	}

	request_->responseHeaders.push_back(name.str(), value.str());

done:
	return true;
}

/** callback, invoked on a new response content chunk. */
bool ProxyConnection::onMessageContent(const x0::BufferRef& chunk)
{
	TRACE("messageContent(nb:%lu) state:%s", chunk.size(), backend_->state_str());

	// stop watching for more input
	backend_->setMode(x0::Socket::None);

	// transfer response-body chunk to client
	request_->write<x0::BufferRefSource>(chunk);

	// start listening on backend I/O when chunk has been fully transmitted
	ref();
	request_->writeCallback<ProxyConnection, &ProxyConnection::onWriteComplete>(this);

	return true;
}

void ProxyConnection::onWriteComplete()
{
	TRACE("chunk write complete: %s", state_str());
	backend_->setMode(x0::Socket::Read);
	unref();
}

bool ProxyConnection::onMessageEnd()
{
	TRACE("messageEnd() backend-state:%s", backend_->state_str());
	processingDone_ = true;
	return false;
}

void ProxyConnection::io(x0::Socket* s, int revents)
{
	TRACE("io(0x%04x)", revents);

	if (revents & x0::Socket::Read)
		readSome();

	if (revents & x0::Socket::Write)
		writeSome();
}

void ProxyConnection::writeSome()
{
	TRACE("writeSome() - %s (%d)", state_str(), request_->contentAvailable());

	ssize_t rv = backend_->write(writeBuffer_.data() + writeOffset_, writeBuffer_.size() - writeOffset_);

	if (rv > 0) {
		TRACE("write request: %ld (of %ld) bytes", rv, writeBuffer_.size() - writeOffset_);

		writeOffset_ += rv;
		writeProgress_ += rv;

		if (writeOffset_ == writeBuffer_.size()) {
			TRACE("writeOffset == writeBuffser.size (%ld) p:%ld, ca: %d, clr:%ld", writeOffset_,
				writeProgress_, request_->contentAvailable(), request_->connection.contentLength());

			writeOffset_ = 0;
			writeBuffer_.clear();
			backend_->setMode(x0::Socket::Read);
		}
	} else {
		TRACE("write request failed(%ld): %s", rv, strerror(errno));
		close();
	}
}

void ProxyConnection::readSome()
{
	TRACE("readSome() - %s", state_str());

	std::size_t lower_bound = readBuffer_.size();

	if (lower_bound == readBuffer_.capacity())
		readBuffer_.setCapacity(lower_bound + 4096);

	ssize_t rv = backend_->read(readBuffer_);

	if (rv > 0) {
		TRACE("read response: %ld bytes", rv);
		std::size_t np = process(readBuffer_.ref(lower_bound, rv));
		(void) np;
		TRACE("readSome(): process(): %ld / %ld", np, rv);

		if (processingDone_ || state() == SYNTAX_ERROR) {
			close();
		} else {
			TRACE("resume with io:%d, state:%s", backend_->mode(), backend_->state_str());
			backend_->setMode(x0::Socket::Read);
		}
	} else if (rv == 0) {
		TRACE("http server connection closed");
		close();
	} else {
		TRACE("read response failed(%ld): %s", rv, strerror(errno));

		switch (errno) {
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
		case EWOULDBLOCK:
#endif
		case EAGAIN:
		case EINTR:
			backend_->setMode(x0::Socket::Read);
			break;
		default:
			close();
			break;
		}
	}
}

// }}}

// {{{ plugin class
/**
 * \ingroup plugins
 * \brief proxy content generator plugin
 */
class proxy_plugin :
	public x0::HttpPlugin
{
private:
	bool cloak_;

public:
	proxy_plugin(x0::HttpServer& srv, const std::string& name) :
		x0::HttpPlugin(srv, name),
		cloak_(true)
	{
		registerHandler<proxy_plugin, &proxy_plugin::proxy_reverse>("proxy.reverse");
		registerSetupProperty<proxy_plugin, &proxy_plugin::proxy_cloak>("proxy.cloak", x0::FlowValue::BOOLEAN);
	}

	~proxy_plugin()
	{
	}

private:
	void proxy_cloak(const x0::FlowParams& args, x0::FlowValue& result)
	{
		if (args.size() && (args[0].isBool() || args[0].isNumber())) {
			cloak_ = args[0].toBool();
			TRACE("proxy cloak: %s", cloak_ ? "true" : "false");
		}

		result.set(cloak_);
	}

	bool proxy_reverse(x0::HttpRequest *in, const x0::FlowParams& args)
	{
		// TODO: reuse already spawned proxy connections instead of recreating each time.
		x0::SocketSpec spec;
		spec << args;
		if (!spec.isValid() || spec.backlog >= 0) {
			in->log(x0::Severity::error, "Invalid socket spec passed.");
			return false;
		}

		x0::Socket* backend = new x0::Socket(in->connection.worker().loop());
		backend->open(spec, O_NONBLOCK | O_CLOEXEC);

		if (backend->isOpen()) {
			TRACE("in.content? %d", in->contentAvailable());
			if (ProxyConnection* pc = new ProxyConnection()) {
				pc->start(in, backend, cloak_);
				return true;
			}
		}

		in->status = x0::HttpError::ServiceUnavailable;
		in->finish();

		return true;
	}
};
// }}}

X0_EXPORT_PLUGIN(proxy)
<|endoftext|>


<commit_before>#include "loader.h"

#include<dlfcn.h>

BVS::ModuleMap BVS::Loader::modules;

BVS::ModuleVector BVS::Loader::masterModules;

BVS::Loader::Loader(Control& control, const Info& info)
	: control(control)
	, logger("Loader")
	, info(info)
{

}

void BVS::Loader::registerModule(const std::string& id, Module* module)
{
	modules[id] = std::shared_ptr<ModuleData>(new ModuleData(
			  id
			, std::string()
			, std::string()
			, module
			, nullptr
			, false
			, ModuleFlag::WAIT
			, Status::NONE
			, ConnectorMap()));
}

BVS::Loader& BVS::Loader::load(const std::string& moduleTraits, const bool asThread)
{
	/* algorithm:
	 * SEPARATE id(library).options
	 * CHECK for duplicate ids
	 * LOAD the library and CHECK errors
	 * LINK and execute register function, CHECK errors
	 * SAVE metadata
	 * MOVE connectors to metadata
	 * START (un/)threaded module
	 */
	std::string id;
	std::string library;
	std::string options;

	// search for '.' in id and separate id and options
	size_t separator = moduleTraits.find_first_of('.');
	if (separator!=std::string::npos)
	{
		id = moduleTraits.substr(0, separator);
		options = moduleTraits.substr(separator+1, std::string::npos);
	}
	else
		id = moduleTraits;

	// search for '(' in id and separate if necessary
	separator = id.find_first_of('(');
	if (separator!=std::string::npos)
	{
		library = id.substr(separator+1, std::string::npos);
		library.erase(library.length()-1);
		id = id.erase(separator, std::string::npos);
	}
	else
		library = id;

	// search for duplicate id in modules
	if (modules.find(id)!=modules.end())
	{
		LOG(0, "Duplicate id for module: " << id);
		LOG(0, "If you try to load a module more than once, use unique ids and the id(library).options syntax!");
		exit(-1);
	}

	// prepare path and load the lib
	std::string modulePath = "./lib" + library + ".so";
	LOG(3, "Loading " << id << " from " << modulePath << "!");
	void* dlib = dlopen(modulePath.c_str(), RTLD_NOW);

	// check for errors
	if (dlib == NULL)
	{
		LOG(0, "Loading " << modulePath << ", resulted in: " << dlerror());
		exit(-1);
	}

	// look for bvsRegisterModule in loaded lib, check for errors and execute register function
	typedef void (*bvsRegisterModule_t)(const std::string& id, const Info& info);
	bvsRegisterModule_t bvsRegisterModule;
	*reinterpret_cast<void**>(&bvsRegisterModule)=dlsym(dlib, "bvsRegisterModule");

	// check for errors
	char* dlerr = dlerror();
	if (dlerr)
	{
		LOG(0, "Loading function bvsRegisterModule() in " << modulePath << " resulted in: " << dlerr);
		exit(-1);
	}

	// register
	bvsRegisterModule(id, info);
	LOG(2, "Loading " << id << " successfull!");

	// save handle,library name and option string for later use
	modules[id]->dlib = dlib;
	modules[id]->library = library;
	modules[id]->options = options;

	// move connectors from temporary to metadata
	modules[id]->connectors = std::move(ConnectorDataCollector::connectors);

	// set metadata and start as thread if needed
	if (asThread==true)
	{
		LOG(3, id << " executed by dedicated thread!");
		modules[id]->asThread = true;
		modules[id]->thread = std::thread(&Control::threadController, &control, modules[id]);
		Control::threadedModules++;
	}
	else
	{
		LOG(3, id << " executed by controller!");
		modules[id]->asThread = false;
		masterModules.push_back(modules[id]);
	}

	return *this;
}

BVS::Loader& BVS::Loader::unload(const std::string& id, const bool eraseFromMap)
{
	/* algorithm:
	 * CHECK thread, signal exit
	 * DISCONNECT connectors
	 * DELETE module instance and connectors
	 * CHECK library handle
	 * CLOSE library
	 * CHECK errors
	 */
	// wait for thread to join, first check if it is still running
	if (modules[id]->asThread == true)
	{
		if (modules[id]->thread.joinable())
		{
			modules[id]->flag = ModuleFlag::QUIT;
			control.threadCond.notify_all();
			LOG(3, "Waiting for " << id << " to join!");
			modules[id]->thread.join();
		}
	}

	// disconnect connectors
	for (auto& it: modules)
	{
		for (auto& con: it.second->connectors)
		{
			if (con.second->type==ConnectorType::INPUT) continue;

			// find and reset all inputs connected to output
			for (auto& mods: modules)
			{
				for (auto& modCon: mods.second->connectors)
				{
					if (con.second->pointer==modCon.second->pointer)
					{
						modCon.second->pointer = nullptr;
						modCon.second->active = false;
						modCon.second->mutex = nullptr;
					}
				}

			}
		}
	}

	// delete module and connectors
	modules[id]->connectors.clear();
	delete modules[id]->module;
	modules[id]->module = nullptr;

	// close lib and check for errors
	std::string modulePath = "./lib" + modules[id]->library + ".so";
	LOG(3, id << " unloading from " << modulePath << "!");

	// get handle from internals
	void* dlib = modules[id]->dlib;
	if (dlib==nullptr)
	{
		LOG(0, "Requested module " << id << " not found!");
		exit(-1);
	}

	// close the module
	dlclose(dlib);

	// check for errors
	char* dlerr = dlerror();
	if (dlerr)
	{
		LOG(0, "While closing " << modulePath << " following error occured: " << dlerror());
		exit(-1);
	}
	LOG(2, id << " unloaded!");

	if (eraseFromMap)
	{
		modules.erase(id);
	}

	return *this;
}

BVS::Loader& BVS::Loader::unloadAll()
{
	for (auto& it: modules)
		unload(it.second->id, false);

	modules.clear();

	return *this;
}

BVS::Loader& BVS::Loader::connectAllModules(const bool connectorTypeMatching)
{
	for (auto& it: modules) connectModule(it.second->id, connectorTypeMatching);

	return *this;
}

BVS::Loader& BVS::Loader::connectModule(const std::string& id, const bool connectorTypeMatching)
{
	/* algorithm:
	 * WHILE option string not empty
	 *	SEPARATE selection
	 * 	CHECK input and output
	 * 	CHECK input typeid hash == output typeid hash
	 * 	CONNECT
	 * DONE
	 */

	ModuleData* module = modules[id].get();
	std::string options = module->options;
	std::string selection;
	std::string input;
	std::string targetModule;
	std::string targetOutput;
	size_t separator;
	size_t separator2;

	while (!options.empty())
	{
		// get input name and selection
		separator = options.find_first_of('(');
		separator2 = options.find_first_of(')');
		selection = options.substr(0, separator2+1);
		if (separator!=std::string::npos)
		{
			input = options.substr(0, separator);
			targetModule= options.substr(separator+1, separator2-separator-1);
		}
		else
		{
			LOG(0, "No input selection found: " << selection);
			exit(1);
		}

		// remove parsed part
		options.erase(0, separator2+1);
		if (options[0] == '.') options.erase(options.begin());

		// search for '.' in selection and separate module and output
		separator = targetModule.find_first_of('.');
		if (separator!=std::string::npos)
		{
			targetOutput = targetModule.substr(separator+1, std::string::npos);
			targetModule = targetModule.substr(0, separator);
		}
		else
		{
			LOG(0, "No module output selected: " << module->id << "." << selection);
			exit(1);
		}

		// check input and output
		checkModuleInput(module, input);
		checkModuleOutput(module, targetModule, targetOutput);

		// check input typeid hash == output typeid hash
		if (connectorTypeMatching && module->connectors[input]->typeIDHash != modules[targetModule]->connectors[targetOutput]->typeIDHash)
		{
			LOG(0, "Selected input and output connector template instantiations are of different type: "
					<< module->id << "." << selection << " -> "
					<< module->connectors[input]->typeIDName << " != " << modules[targetModule]->connectors[targetOutput]->typeIDName);
			exit(1);
		}

		// connect
		module->connectors[input]->pointer = modules[targetModule]->connectors[targetOutput]->pointer;
		module->connectors[input]->mutex = modules[targetModule]->connectors[targetOutput]->mutex;
		LOG(3, "Connected: " << module->id << "." << module->connectors[input]->id << " <- " << modules[targetModule]->id << "." << modules[targetModule]->connectors[targetOutput]->id);
	}

	return *this;
}

BVS::Loader& BVS::Loader::printModuleConnectors(const ModuleData* module)
{
	if (module->connectors.size()==0)
		LOG(0, "Module " << module->id << " does not define any connector!");
	else
	{
		LOG(0, "Module " << module->id << " defines the following connectors: ");
		for (auto& it: module->connectors) LOG(0, it.second->type << ": " << it.second->id);
	}

	return *this;
}

BVS::Loader& BVS::Loader::checkModuleInput(const ModuleData* module, const std::string& inputName)
{
	auto input = module->connectors.find(inputName);

	// check existence and type
	if (input == module->connectors.end() || input->second->type != ConnectorType::INPUT)
	{
		LOG(0, "Input not found: " << module->id << "." << inputName);
		printModuleConnectors(module);
		exit(1);
	}

	// check if input is already connected
	if (input->second->active)
	{
		LOG(0, "Input already connected: " << module->id << "." << inputName);
		exit(1);
	}

	return *this;
}

BVS::Loader& BVS::Loader::checkModuleOutput(const ModuleData* module, const std::string& targetModule, const std::string& targetOutput)
{
	auto target = modules.find(targetModule);

	// check if desired module exists
	if (target == modules.end())
	{
		LOG(0, "Module not found: " << targetModule << " in " << module->id << "." << module->options);
		exit(1);
	}

	auto output = target->second->connectors.find(targetOutput);

	// check output existence and type
	if (output == target->second->connectors.end() || output->second->type != ConnectorType::OUTPUT)
	{
		LOG(0, "Output not found: " << targetOutput << " in " << module->id << "." << module->options);
		printModuleConnectors(target->second.get());
		exit(1);
	}

	return *this;
}

<commit_msg>loader: fix syntax error<commit_after>#include "loader.h"

#include<dlfcn.h>

BVS::ModuleMap BVS::Loader::modules;

BVS::ModuleVector BVS::Loader::masterModules;

BVS::Loader::Loader(Control& control, const Info& info)
	: control(control)
	, logger("Loader")
	, info(info)
{

}

void BVS::Loader::registerModule(const std::string& id, Module* module)
{
	modules[id] = std::shared_ptr<ModuleData>(new ModuleData(
			  id
			, std::string()
			, std::string()
			, module
			, nullptr
			, false
			, ModuleFlag::WAIT
			, Status::NONE
			, ConnectorMap()));
}

BVS::Loader& BVS::Loader::load(const std::string& moduleTraits, const bool asThread)
{
	/* algorithm:
	 * SEPARATE id(library).options
	 * CHECK for duplicate ids
	 * LOAD the library and CHECK errors
	 * LINK and execute register function, CHECK errors
	 * SAVE metadata
	 * MOVE connectors to metadata
	 * START (un/)threaded module
	 */
	std::string id;
	std::string library;
	std::string options;

	// search for '.' in id and separate id and options
	size_t separator = moduleTraits.find_first_of('.');
	if (separator!=std::string::npos)
	{
		id = moduleTraits.substr(0, separator);
		options = moduleTraits.substr(separator+1, std::string::npos);
	}
	else
		id = moduleTraits;

	// search for '(' in id and separate if necessary
	separator = id.find_first_of('(');
	if (separator!=std::string::npos)
	{
		library = id.substr(separator+1, std::string::npos);
		library.erase(library.length()-1);
		id = id.erase(separator, std::string::npos);
	}
	else
		library = id;

	// search for duplicate id in modules
	if (modules.find(id)!=modules.end())
	{
		LOG(0, "Duplicate id for module: " << id);
		LOG(0, "If you try to load a module more than once, use unique ids and the id(library).options syntax!");
		exit(-1);
	}

	// prepare path and load the lib
	std::string modulePath = "./lib" + library + ".so";
	LOG(3, "Loading " << id << " from " << modulePath << "!");
	void* dlib = dlopen(modulePath.c_str(), RTLD_NOW);

	// check for errors
	if (dlib == NULL)
	{
		LOG(0, "Loading " << modulePath << ", resulted in: " << dlerror());
		exit(-1);
	}

	// look for bvsRegisterModule in loaded lib, check for errors and execute register function
	typedef void (*bvsRegisterModule_t)(const std::string& id, const Info& info);
	bvsRegisterModule_t bvsRegisterModule;
	*reinterpret_cast<void**>(&bvsRegisterModule)=dlsym(dlib, "bvsRegisterModule");

	// check for errors
	char* dlerr = dlerror();
	if (dlerr)
	{
		LOG(0, "Loading function bvsRegisterModule() in " << modulePath << " resulted in: " << dlerr);
		exit(-1);
	}

	// register
	bvsRegisterModule(id, info);
	LOG(2, "Loading " << id << " successfull!");

	// save handle,library name and option string for later use
	modules[id]->dlib = dlib;
	modules[id]->library = library;
	modules[id]->options = options;

	// move connectors from temporary to metadata
	modules[id]->connectors = std::move(ConnectorDataCollector::connectors);

	// set metadata and start as thread if needed
	if (asThread==true)
	{
		LOG(3, id << " executed by dedicated thread!");
		modules[id]->asThread = true;
		modules[id]->thread = std::thread(&Control::threadController, &control, modules[id]);
		Control::threadedModules++;
	}
	else
	{
		LOG(3, id << " executed by controller!");
		modules[id]->asThread = false;
		masterModules.push_back(modules[id]);
	}

	return *this;
}

BVS::Loader& BVS::Loader::unload(const std::string& id, const bool eraseFromMap)
{
	/* algorithm:
	 * CHECK thread, signal exit
	 * DISCONNECT connectors
	 * DELETE module instance and connectors
	 * CHECK library handle
	 * CLOSE library
	 * CHECK errors
	 */
	// wait for thread to join, first check if it is still running
	if (modules[id]->asThread == true)
	{
		if (modules[id]->thread.joinable())
		{
			modules[id]->flag = ModuleFlag::QUIT;
			control.threadCond.notify_all();
			LOG(3, "Waiting for " << id << " to join!");
			modules[id]->thread.join();
		}
	}

	// disconnect connectors
	for (auto& it: modules)
	{
		for (auto& con: it.second->connectors)
		{
			if (con.second->type==ConnectorType::INPUT) continue;

			// find and reset all inputs connected to output
			for (auto& mods: modules)
			{
				for (auto& modCon: mods.second->connectors)
				{
					if (con.second->pointer==modCon.second->pointer)
					{
						modCon.second->pointer = nullptr;
						modCon.second->active = false;
						modCon.second->mutex = nullptr;
					}
				}

			}
		}
	}

	// delete module and connectors
	modules[id]->connectors.clear();
	delete modules[id]->module;
	modules[id]->module = nullptr;

	// close lib and check for errors
	std::string modulePath = "./lib" + modules[id]->library + ".so";
	LOG(3, id << " unloading from " << modulePath << "!");

	// get handle from internals
	void* dlib = modules[id]->dlib;
	if (dlib==nullptr)
	{
		LOG(0, "Requested module " << id << " not found!");
		exit(-1);
	}

	// close the module
	dlclose(dlib);

	// check for errors
	char* dlerr = dlerror();
	if (dlerr)
	{
		LOG(0, "While closing " << modulePath << " following error occured: " << dlerror());
		exit(-1);
	}
	LOG(2, id << " unloaded!");

	if (eraseFromMap)
	{
		modules.erase(id);
	}

	return *this;
}

BVS::Loader& BVS::Loader::unloadAll()
{
	for (auto& it: modules)
		unload(it.second->id, false);

	modules.clear();

	return *this;
}

BVS::Loader& BVS::Loader::connectAllModules(const bool connectorTypeMatching)
{
	for (auto& it: modules) connectModule(it.second->id, connectorTypeMatching);

	return *this;
}

BVS::Loader& BVS::Loader::connectModule(const std::string& id, const bool connectorTypeMatching)
{
	/* algorithm:
	 * WHILE option string not empty
	 *	SEPARATE selection
	 * 	CHECK input and output
	 * 	CHECK input typeid hash == output typeid hash
	 * 	CONNECT
	 * DONE
	 */

	ModuleData* module = modules[id].get();
	std::string options = module->options;
	std::string selection;
	std::string input;
	std::string targetModule;
	std::string targetOutput;
	size_t separator;
	size_t separator2;

	while (!options.empty())
	{
		// get input name and selection
		separator = options.find_first_of('(');
		separator2 = options.find_first_of(')');
		selection = options.substr(0, separator2+1);
		if (separator!=std::string::npos)
		{
			input = options.substr(0, separator);
			targetModule= options.substr(separator+1, separator2-separator-1);
		}
		else
		{
			LOG(0, "No input selection found: " << selection);
			exit(1);
		}

		// remove parsed part
		options.erase(0, separator2+1);
		if (options[0] == '.') options.erase(options.begin());

		// search for '.' in selection and separate module and output
		separator = targetModule.find_first_of('.');
		if (separator!=std::string::npos)
		{
			targetOutput = targetModule.substr(separator+1, std::string::npos);
			targetModule = targetModule.substr(0, separator);
		}
		else
		{
			LOG(0, "No module output selected: " << module->id << "." << selection);
			exit(1);
		}

		// check input and output
		checkModuleInput(module, input);
		checkModuleOutput(module, targetModule, targetOutput);

		// check input typeid hash == output typeid hash
		if (connectorTypeMatching && module->connectors[input]->typeIDHash != modules[targetModule]->connectors[targetOutput]->typeIDHash)
		{
			LOG(0, "Selected input and output connector template instantiations are of different type: "
					<< module->id << "." << selection << " -> "
					<< module->connectors[input]->typeIDName << " != " << modules[targetModule]->connectors[targetOutput]->typeIDName);
			exit(1);
		}

		// connect
		module->connectors[input]->pointer = modules[targetModule]->connectors[targetOutput]->pointer;
		module->connectors[input]->mutex = modules[targetModule]->connectors[targetOutput]->mutex;
		LOG(3, "Connected: " << module->id << "." << module->connectors[input]->id << " <- " << modules[targetModule]->id << "." << modules[targetModule]->connectors[targetOutput]->id);
	}

	return *this;
}

BVS::Loader& BVS::Loader::printModuleConnectors(const ModuleData* module)
{
	if (module->connectors.size()==0)
	{
		LOG(0, "Module " << module->id << " does not define any connector!");
	}
	else
	{
		LOG(0, "Module " << module->id << " defines the following connectors: ");
		for (auto& it: module->connectors) LOG(0, it.second->type << ": " << it.second->id);
	}

	return *this;
}

BVS::Loader& BVS::Loader::checkModuleInput(const ModuleData* module, const std::string& inputName)
{
	auto input = module->connectors.find(inputName);

	// check existence and type
	if (input == module->connectors.end() || input->second->type != ConnectorType::INPUT)
	{
		LOG(0, "Input not found: " << module->id << "." << inputName);
		printModuleConnectors(module);
		exit(1);
	}

	// check if input is already connected
	if (input->second->active)
	{
		LOG(0, "Input already connected: " << module->id << "." << inputName);
		exit(1);
	}

	return *this;
}

BVS::Loader& BVS::Loader::checkModuleOutput(const ModuleData* module, const std::string& targetModule, const std::string& targetOutput)
{
	auto target = modules.find(targetModule);

	// check if desired module exists
	if (target == modules.end())
	{
		LOG(0, "Module not found: " << targetModule << " in " << module->id << "." << module->options);
		exit(1);
	}

	auto output = target->second->connectors.find(targetOutput);

	// check output existence and type
	if (output == target->second->connectors.end() || output->second->type != ConnectorType::OUTPUT)
	{
		LOG(0, "Output not found: " << targetOutput << " in " << module->id << "." << module->options);
		printModuleConnectors(target->second.get());
		exit(1);
	}

	return *this;
}

<|endoftext|>


<commit_before>#include <SDL_thread.h>
#include "StdinCLISupport.h"
#include "ThreadPool.h"
#include "Mutex.h"
#include "OLXCommand.h"
#include "Debug.h"
#include "InputEvents.h"

static bool hasStdinCLISupport = false;

bool stdinCLIActive() { return hasStdinCLISupport; }

#ifndef HAVE_LINENOISE
Result initStdinCLISupport() {
	return "not implemented";
}

void quitStdinCLISupport() {}

StdinCLI_StdoutScope::StdinCLI_StdoutScope() {}
StdinCLI_StdoutScope::~StdinCLI_StdoutScope() {}
#else

#include <unistd.h>
#include <sys/select.h>
#include <linenoise.h>

struct StdinCmdLineIntf : CmdLineIntf {
	void pushReturnArg(const std::string& str) {
		notes << "CLI return: " << str << endl;
	}

	void finalizeReturn() {
		notes << "CLI return." << endl;
	}

	void writeMsg(const std::string& str, CmdLineMsgType type) {
		// TODO: handle type
		hints << "CLI: " << str << endl;
	}

};
StdinCmdLineIntf stdinCLI;

static Mutex stdoutMutex;
static bool stdoutInRawMode = false;
static bool quit = false;

struct LinenoiseEnvCustom : LinenoiseEnv {
	LinenoiseEnvCustom() {
		prompt = "OLX> ";
	}
	virtual ssize_t read(void* d, size_t nbyte) {
		Mutex::ScopedUnlock unlock(stdoutMutex);

		while(true) {
			struct timeval time;
			time.tv_sec = 0;
			time.tv_usec = 10 * 1000; // 10ms
			fd_set set;
			FD_ZERO(&set);
			FD_SET(fd, &set);
			int r = select(fd+1, &set, NULL, NULL, &time);
			{
				Mutex::ScopedLock lock(stdoutMutex);
				if(r > 0)
					return LinenoiseEnv::read(d, nbyte);
				if(r < 0)
					return -1;
				if(quit) return 0;
			}
		}
		return -1;
	}
};

LinenoiseEnvCustom linenoiseEnv;

StdinCLI_StdoutScope::StdinCLI_StdoutScope() {
	stdoutMutex.lock();
	if(stdoutInRawMode) {
		linenoiseEnv.eraseLine();
		linenoiseDisableRawMode(linenoiseEnv.fd);
	}
}

StdinCLI_StdoutScope::~StdinCLI_StdoutScope() {
	if(stdoutInRawMode) {
		linenoiseEnableRawMode(linenoiseEnv.fd);
		linenoiseEnv.refreshLine();
	}
	stdoutMutex.unlock();
}

void linenoiseCompletionCallbackFunc(const std::string& buf, LinenoiseCompletions* lc) {
	Mutex::ScopedUnlock unlock(stdoutMutex);

}

int handleStdin(void*) {
	while(!linenoiseEnv.hadReadError) {
		std::string buf;
		{
			Mutex::ScopedLock lock(stdoutMutex);
			if(quit) break;
			stdoutInRawMode = true;
			buf = linenoiseEnv.getNextInput();
			stdoutInRawMode = false;
		}
		if(!buf.empty()) {
			Execute( &stdinCLI, buf );
			WakeupIfNeeded();
		}
	}
	if(!quit)
		notes << "CLI read error. quit" << endl;
	return 0;
}

static SDL_Thread* stdinHandleThread = NULL;

Result initStdinCLISupport() {
	if(!isatty(STDIN_FILENO))
		return "stdin is not a terminal type device";

	if(linenoiseIsUnsupportedTerm())
		return "linenoise does not support this type of terminal";

	linenoiseSetCompletionCallback(linenoiseCompletionCallbackFunc);

	stdinHandleThread = SDL_CreateThread(handleStdin, NULL);
	if(stdinHandleThread == NULL)
		return "couldn't create thread";

	hasStdinCLISupport = true;
	return true;
}

void quitStdinCLISupport() {
	if(!hasStdinCLISupport) return;
	if(!stdinHandleThread) return;

	notes << "wait for StdinCLI quit" << endl;
	{
		Mutex::ScopedLock lock(stdoutMutex);
		quit = true;
	}
	SDL_WaitThread(stdinHandleThread, NULL);
	stdinHandleThread = NULL;
	hasStdinCLISupport = false;
}

#endif // HAVE_LINENOISE
<commit_msg>StdinCLI support: autocompletion support<commit_after>#include <SDL_thread.h>
#include "StdinCLISupport.h"
#include "ThreadPool.h"
#include "Mutex.h"
#include "OLXCommand.h"
#include "Debug.h"
#include "InputEvents.h"
#include "Autocompletion.h"

static bool hasStdinCLISupport = false;

bool stdinCLIActive() { return hasStdinCLISupport; }

#ifndef HAVE_LINENOISE
Result initStdinCLISupport() {
	return "not implemented";
}

void quitStdinCLISupport() {}

StdinCLI_StdoutScope::StdinCLI_StdoutScope() {}
StdinCLI_StdoutScope::~StdinCLI_StdoutScope() {}
#else

#include <unistd.h>
#include <sys/select.h>
#include <linenoise.h>

struct StdinCmdLineIntf : CmdLineIntf {
	void pushReturnArg(const std::string& str) {
		notes << "CLI return: " << str << endl;
	}

	void finalizeReturn() {
		notes << "CLI return." << endl;
	}

	void writeMsg(const std::string& str, CmdLineMsgType type) {
		// TODO: handle type
		hints << "CLI: " << str << endl;
	}

};
StdinCmdLineIntf stdinCLI;

static Mutex stdoutMutex;
static bool stdoutInRawMode = false;
static bool quit = false;

struct LinenoiseEnvCustom : LinenoiseEnv {
	LinenoiseEnvCustom() {
		prompt = "OLX> ";
	}
	virtual ssize_t read(void* d, size_t nbyte) {
		Mutex::ScopedUnlock unlock(stdoutMutex);

		while(true) {
			struct timeval time;
			time.tv_sec = 0;
			time.tv_usec = 10 * 1000; // 10ms
			fd_set set;
			FD_ZERO(&set);
			FD_SET(fd, &set);
			int r = select(fd+1, &set, NULL, NULL, &time);
			{
				Mutex::ScopedLock lock(stdoutMutex);
				if(r > 0)
					return LinenoiseEnv::read(d, nbyte);
				if(r < 0)
					return -1;
				if(quit) return 0;
			}
		}
		return -1;
	}
};

LinenoiseEnvCustom linenoiseEnv;

StdinCLI_StdoutScope::StdinCLI_StdoutScope() {
	stdoutMutex.lock();
	if(stdoutInRawMode) {
		linenoiseEnv.eraseLine();
		linenoiseDisableRawMode(linenoiseEnv.fd);
	}
}

StdinCLI_StdoutScope::~StdinCLI_StdoutScope() {
	if(stdoutInRawMode) {
		linenoiseEnableRawMode(linenoiseEnv.fd);
		linenoiseEnv.refreshLine();
	}
	stdoutMutex.unlock();
}

bool linenoiseCompletionCallbackFunc(const std::string& buf, LinenoiseCompletions* lc) {
	Mutex::ScopedUnlock unlock(stdoutMutex);

	// Auto-complete
	AutocompletionInfo autoCompleteInfo;
	AutoComplete(linenoiseEnv.buf, linenoiseEnv.pos, stdinCLI, autoCompleteInfo);
	bool fail = false;
	AutocompletionInfo::InputState oldState(linenoiseEnv.buf, linenoiseEnv.pos);
	AutocompletionInfo::InputState newState;
	autoCompleteInfo.popWait(oldState, newState, fail);
	if(!fail) {
		Mutex::ScopedLock lock(stdoutMutex);
		linenoiseEnv.buf = newState.text;
		linenoiseEnv.pos = newState.pos;
		linenoiseEnv.refreshLine();
		return true;
	}

	return false;
}

int handleStdin(void*) {
	while(!linenoiseEnv.hadReadError) {
		std::string buf;
		{
			Mutex::ScopedLock lock(stdoutMutex);
			if(quit) break;
			stdoutInRawMode = true;
			buf = linenoiseEnv.getNextInput();
			stdoutInRawMode = false;
		}
		if(!buf.empty()) {
			Execute( &stdinCLI, buf );
			WakeupIfNeeded();
		}
	}
	if(!quit)
		notes << "CLI read error. quit" << endl;
	return 0;
}

static SDL_Thread* stdinHandleThread = NULL;

Result initStdinCLISupport() {
	if(!isatty(STDIN_FILENO))
		return "stdin is not a terminal type device";

	if(linenoiseIsUnsupportedTerm())
		return "linenoise does not support this type of terminal";

	linenoiseSetCompletionCallback(linenoiseCompletionCallbackFunc);

	stdinHandleThread = SDL_CreateThread(handleStdin, NULL);
	if(stdinHandleThread == NULL)
		return "couldn't create thread";

	hasStdinCLISupport = true;
	return true;
}

void quitStdinCLISupport() {
	if(!hasStdinCLISupport) return;
	if(!stdinHandleThread) return;

	notes << "wait for StdinCLI quit" << endl;
	{
		Mutex::ScopedLock lock(stdoutMutex);
		quit = true;
	}
	SDL_WaitThread(stdinHandleThread, NULL);
	stdinHandleThread = NULL;
	hasStdinCLISupport = false;
}

#endif // HAVE_LINENOISE
<|endoftext|>