From b3aca9ef4860a3d85933777a47a60f06a154ae52 Mon Sep 17 00:00:00 2001 From: Mariusz Zaborski Date: Sun, 6 Feb 2022 14:31:19 -0500 Subject: [PATCH 01/23] zopfli: introduce a wrapper The goal is to use a file descriptor as an input. Thanks to that we shouldn't send a chunk of memory over expensive protocol. --- contrib/zopfli/CMakeLists.txt | 6 ++- contrib/zopfli/example/main.cc | 52 +++++++++++++----- contrib/zopfli/sandboxed.h | 3 +- contrib/zopfli/test/zopfli_test.cc | 69 +++++++++++++++++++++++- contrib/zopfli/utils/utils_zopfli.cc | 22 ++++++++ contrib/zopfli/utils/utils_zopfli.h | 3 ++ contrib/zopfli/wrapper/CMakeLists.txt | 28 ++++++++++ contrib/zopfli/wrapper/wrapper_zopfli.cc | 50 +++++++++++++++++ contrib/zopfli/wrapper/wrapper_zopfli.h | 25 +++++++++ 9 files changed, 243 insertions(+), 15 deletions(-) create mode 100644 contrib/zopfli/wrapper/CMakeLists.txt create mode 100644 contrib/zopfli/wrapper/wrapper_zopfli.cc create mode 100644 contrib/zopfli/wrapper/wrapper_zopfli.h diff --git a/contrib/zopfli/CMakeLists.txt b/contrib/zopfli/CMakeLists.txt index 4de454d..995ffc1 100644 --- a/contrib/zopfli/CMakeLists.txt +++ b/contrib/zopfli/CMakeLists.txt @@ -32,6 +32,8 @@ FetchContent_Declare(zopfli ) FetchContent_MakeAvailable(zopfli) +add_subdirectory(wrapper) + add_sapi_library( sapi_zopfli @@ -44,12 +46,14 @@ add_sapi_library( ZopfliZlibCompress ZopfliGzipCompress + ZopfliCompressFD INPUTS ${zopfli_SOURCE_DIR}/src/zopfli/deflate.h ${zopfli_SOURCE_DIR}/src/zopfli/gzip_container.h ${zopfli_SOURCE_DIR}/src/zopfli/zlib_container.h + wrapper/wrapper_zopfli.h - LIBRARY Zopfli::libzopfli + LIBRARY wrapper_zopfli LIBRARY_NAME Zopfli NAMESPACE "" ) diff --git a/contrib/zopfli/example/main.cc b/contrib/zopfli/example/main.cc index 81b29ab..12469b8 100644 --- a/contrib/zopfli/example/main.cc +++ b/contrib/zopfli/example/main.cc @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include @@ -23,9 +24,39 @@ #include "contrib/zopfli/sandboxed.h" #include "contrib/zopfli/utils/utils_zopfli.h" +ABSL_FLAG(bool, stream, false, "stream memory to sandbox"); ABSL_FLAG(bool, zlib, false, "zlib compression"); ABSL_FLAG(bool, gzip, false, "gzip compression"); +absl::Status CompressMain(ZopfliApi& api, std::string& infile_s, + std::string& outfile_s, ZopfliFormat format) { + std::ifstream infile(infile_s, std::ios::binary); + if (!infile.is_open()) { + return absl::UnavailableError(absl::StrCat("Unable to open ", infile_s)); + } + std::ofstream outfile(outfile_s, std::ios::binary); + if (!outfile.is_open()) { + return absl::UnavailableError(absl::StrCat("Unable to open ", outfile_s)); + } + + return Compress(api, infile, outfile, format); +} + +absl::Status CompressMainFD(ZopfliApi& api, std::string& infile_s, + std::string& outfile_s, ZopfliFormat format) { + sapi::v::Fd infd(open(infile_s.c_str(), O_RDONLY)); + if (infd.GetValue() < 0) { + return absl::UnavailableError(absl::StrCat("Unable to open ", infile_s)); + } + + sapi::v::Fd outfd(open(outfile_s.c_str(), O_WRONLY | O_CREAT)); + if (outfd.GetValue() < 0) { + return absl::UnavailableError(absl::StrCat("Unable to open ", outfile_s)); + } + + return (CompressFD(api, infd, outfd, format)); +} + int main(int argc, char* argv[]) { std::string prog_name(argv[0]); google::InitGoogleLogging(argv[0]); @@ -36,22 +67,13 @@ int main(int argc, char* argv[]) { return EXIT_FAILURE; } - std::ifstream infile(args[1], std::ios::binary); - if (!infile.is_open()) { - std::cerr << "Unable to open " << args[1] << std::endl; - return EXIT_FAILURE; - } - std::ofstream outfile(args[2], std::ios::binary); - if (!outfile.is_open()) { - std::cerr << "Unable to open " << args[2] << std::endl; - return EXIT_FAILURE; - } - ZopfliSapiSandbox sandbox; if (!sandbox.Init().ok()) { std::cerr << "Unable to start sandbox\n"; return EXIT_FAILURE; } + std::string infile_s(args[1]); + std::string outfile_s(args[2]); ZopfliApi api(&sandbox); @@ -62,7 +84,13 @@ int main(int argc, char* argv[]) { format = ZOPFLI_FORMAT_GZIP; } - absl::Status status = Compress(api, infile, outfile, format); + absl::Status status; + if (absl::GetFlag(FLAGS_stream)) { + status = CompressMain(api, infile_s, outfile_s, format); + } else { + status = CompressMainFD(api, infile_s, outfile_s, format); + } + if (!status.ok()) { std::cerr << "Unable to compress file.\n"; std::cerr << status << std::endl; diff --git a/contrib/zopfli/sandboxed.h b/contrib/zopfli/sandboxed.h index e74d3ec..80442e3 100644 --- a/contrib/zopfli/sandboxed.h +++ b/contrib/zopfli/sandboxed.h @@ -28,12 +28,13 @@ class ZopfliSapiSandbox : public ZopfliSandbox { std::unique_ptr ModifyPolicy( sandbox2::PolicyBuilder *) override { return sandbox2::PolicyBuilder() - .AllowStaticStartup() + .AllowDynamicStartup() .AllowWrite() .AllowExit() .AllowMmap() .AllowSystemMalloc() .AllowSyscalls({ + __NR_recvmsg, __NR_sysinfo, }) #ifdef __NR_open diff --git a/contrib/zopfli/test/zopfli_test.cc b/contrib/zopfli/test/zopfli_test.cc index 0c060ff..2986cec 100644 --- a/contrib/zopfli/test/zopfli_test.cc +++ b/contrib/zopfli/test/zopfli_test.cc @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + #include #include "contrib/zopfli/sandboxed.h" @@ -41,8 +43,9 @@ std::string GetTemporaryFile(const std::string& filename) { } class TestText : public testing::TestWithParam {}; - class TestBinary : public testing::TestWithParam {}; +class TestTextFD : public testing::TestWithParam {}; +class TestBinaryFD : public testing::TestWithParam {}; TEST_P(TestText, Compress) { ZopfliSapiSandbox sandbox; @@ -95,4 +98,68 @@ INSTANTIATE_TEST_SUITE_P(SandboxTest, TestBinary, testing::Values(ZOPFLI_FORMAT_DEFLATE, ZOPFLI_FORMAT_GZIP, ZOPFLI_FORMAT_ZLIB)); + +TEST_P(TestTextFD, Compress) { + ZopfliSapiSandbox sandbox; + ASSERT_THAT(sandbox.Init(), IsOk()) << "Couldn't initialize Sandboxed API"; + ZopfliApi api = ZopfliApi(&sandbox); + + std::string infile_s = GetTestFilePath("text"); + std::string outfile_s = GetTemporaryFile("text.out"); + ASSERT_THAT(outfile_s, Not(IsEmpty())); + + sapi::v::Fd infd(open(infile_s.c_str(), O_RDONLY)); + ASSERT_GE(infd.GetValue(), 0); + + sapi::v::Fd outfd(open(outfile_s.c_str(), O_WRONLY)); + ASSERT_GE(outfd.GetValue(), 0); + + absl::Status status = CompressFD(api, infd, outfd, GetParam()); + ASSERT_THAT(status, IsOk()) << "Unable to compress file"; + + off_t inpos = lseek(infd.GetValue(), 0, SEEK_END); + EXPECT_GE(inpos, 0); + + off_t outpos = lseek(outfd.GetValue(), 0, SEEK_END); + EXPECT_GE(outpos, 0); + + EXPECT_LT(outpos, inpos); +} + +INSTANTIATE_TEST_SUITE_P(SandboxTest, TestTextFD, + testing::Values(ZOPFLI_FORMAT_DEFLATE, + ZOPFLI_FORMAT_GZIP, + ZOPFLI_FORMAT_ZLIB)); + +TEST_P(TestBinaryFD, Compress) { + ZopfliSapiSandbox sandbox; + ASSERT_THAT(sandbox.Init(), IsOk()) << "Couldn't initialize Sandboxed API"; + ZopfliApi api = ZopfliApi(&sandbox); + + std::string infile_s = GetTestFilePath("binary"); + std::string outfile_s = GetTemporaryFile("binary.out"); + ASSERT_THAT(outfile_s, Not(IsEmpty())); + + sapi::v::Fd infd(open(infile_s.c_str(), O_RDONLY)); + ASSERT_GE(infd.GetValue(), 0); + + sapi::v::Fd outfd(open(outfile_s.c_str(), O_WRONLY)); + ASSERT_GE(outfd.GetValue(), 0); + + absl::Status status = CompressFD(api, infd, outfd, GetParam()); + ASSERT_THAT(status, IsOk()) << "Unable to compress file"; + + off_t inpos = lseek(infd.GetValue(), 0, SEEK_END); + EXPECT_GE(inpos, 0); + + off_t outpos = lseek(outfd.GetValue(), 0, SEEK_END); + EXPECT_GE(outpos, 0); + + EXPECT_LT(outpos, inpos); +} + +INSTANTIATE_TEST_SUITE_P(SandboxTest, TestBinaryFD, + testing::Values(ZOPFLI_FORMAT_DEFLATE, + ZOPFLI_FORMAT_GZIP, + ZOPFLI_FORMAT_ZLIB)); } // namespace diff --git a/contrib/zopfli/utils/utils_zopfli.cc b/contrib/zopfli/utils/utils_zopfli.cc index ef41b18..1a79b01 100644 --- a/contrib/zopfli/utils/utils_zopfli.cc +++ b/contrib/zopfli/utils/utils_zopfli.cc @@ -54,3 +54,25 @@ absl::Status Compress(ZopfliApi& api, std::ifstream& instream, } return absl::OkStatus(); } + +absl::Status CompressFD(ZopfliApi& api, sapi::v::Fd& infd, sapi::v::Fd& outfd, + ZopfliFormat format) { + SAPI_RETURN_IF_ERROR(api.GetSandbox()->TransferToSandboxee(&infd)); + SAPI_RETURN_IF_ERROR(api.GetSandbox()->TransferToSandboxee(&outfd)); + + sapi::v::Struct options; + SAPI_RETURN_IF_ERROR(api.ZopfliInitOptions(options.PtrAfter())); + + SAPI_ASSIGN_OR_RETURN( + int ret, api.ZopfliCompressFD(options.PtrBefore(), format, + infd.GetRemoteFd(), outfd.GetRemoteFd())); + + infd.CloseRemoteFd(api.GetSandbox()->rpc_channel()).IgnoreError(); + outfd.CloseRemoteFd(api.GetSandbox()->rpc_channel()).IgnoreError(); + + if (ret == -1) { + return absl::UnavailableError("Unable to compress file"); + } + + return absl::OkStatus(); +} diff --git a/contrib/zopfli/utils/utils_zopfli.h b/contrib/zopfli/utils/utils_zopfli.h index 664e861..5095808 100644 --- a/contrib/zopfli/utils/utils_zopfli.h +++ b/contrib/zopfli/utils/utils_zopfli.h @@ -23,4 +23,7 @@ absl::Status Compress(ZopfliApi& api, std::ifstream& instream, std::ofstream& outstream, ZopfliFormat format); +absl::Status CompressFD(ZopfliApi& api, sapi::v::Fd& infd, sapi::v::Fd& outfd, + ZopfliFormat format); + #endif // CONTRIB_ZOPFLI_UTILS_UTILS_ZOPFLI_H_ diff --git a/contrib/zopfli/wrapper/CMakeLists.txt b/contrib/zopfli/wrapper/CMakeLists.txt new file mode 100644 index 0000000..a9f23df --- /dev/null +++ b/contrib/zopfli/wrapper/CMakeLists.txt @@ -0,0 +1,28 @@ +# Copyright 2022 Google LLC +# +# 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 +# +# https://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. + +add_library( + wrapper_zopfli STATIC + + wrapper_zopfli.cc +) + +target_link_libraries(wrapper_zopfli PUBLIC + Zopfli::libzopfli +) + +target_include_directories(wrapper_zopfli PUBLIC + ${SAPI_SOURCE_DIR} + ${libzopfli_SOURCE_DIR}/src/zopfli +) diff --git a/contrib/zopfli/wrapper/wrapper_zopfli.cc b/contrib/zopfli/wrapper/wrapper_zopfli.cc new file mode 100644 index 0000000..ccc9dea --- /dev/null +++ b/contrib/zopfli/wrapper/wrapper_zopfli.cc @@ -0,0 +1,50 @@ +// Copyright 2022 Google LLC +// +// 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 +// +// https://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 "contrib/zopfli/wrapper/wrapper_zopfli.h" + +#include +#include + +#include +#include +#include + +int ZopfliCompressFD(const ZopfliOptions* options, ZopfliFormat output_type, + int infd, int outfd) { + off_t insize = lseek(infd, 0, SEEK_END); + if (insize < 0) { + return -1; + } + if (lseek(infd, 0, SEEK_SET) < 0) { + return -1; + } + + auto inbuf = std::make_unique(insize); + if (read(infd, inbuf.get(), insize) != insize) { + return -1; + } + + size_t outsize = 0; + uint8_t* outbuf = nullptr; + ZopfliCompress(options, output_type, inbuf.get(), insize, &outbuf, &outsize); + size_t retsize = write(outfd, outbuf, outsize); + free(outbuf); + + if (outsize != retsize) { + return -1; + } + + return 0; +} diff --git a/contrib/zopfli/wrapper/wrapper_zopfli.h b/contrib/zopfli/wrapper/wrapper_zopfli.h new file mode 100644 index 0000000..0bb02fe --- /dev/null +++ b/contrib/zopfli/wrapper/wrapper_zopfli.h @@ -0,0 +1,25 @@ +// Copyright 2022 Google LLC +// +// 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 +// +// https://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 CONTRIB_ZOPFLI_WRAPPER_WRAPPER_ZOPFLI_H_ +#define CONTRIB_ZOPFLI_WRAPPER_WRAPPER_ZOPFLI_H_ + +#include "zopfli.h" + +extern "C" { +int ZopfliCompressFD(const ZopfliOptions* options, ZopfliFormat output_type, + int infd, int outfd); +}; + +#endif // CONTRIB_ZOPFLI_WRAPPER_WRAPPER_ZOPFLI_H_ From 5c154af744d1e23e05070e2d3bbb0d71d824831f Mon Sep 17 00:00:00 2001 From: Mariusz Zaborski Date: Wed, 2 Feb 2022 15:45:58 -0500 Subject: [PATCH 02/23] ZStandard: introduce a wrapper The goal is to use a file descriptor as an input for ZStandard library. Thanks to that we shouldn't send a chunk of memory over expensive protocol. --- contrib/zstd/CMakeLists.txt | 10 +- contrib/zstd/example/main.cc | 72 ++++++--- contrib/zstd/sandboxed.h | 4 + contrib/zstd/test/zstd_test.cc | 234 +++++++++++++++++++++++++++ contrib/zstd/utils/utils_zstd.cc | 96 ++++++++++- contrib/zstd/utils/utils_zstd.h | 8 + contrib/zstd/wrapper/CMakeLists.txt | 27 ++++ contrib/zstd/wrapper/wrapper_zstd.cc | 184 +++++++++++++++++++++ contrib/zstd/wrapper/wrapper_zstd.h | 28 ++++ 9 files changed, 640 insertions(+), 23 deletions(-) create mode 100644 contrib/zstd/wrapper/CMakeLists.txt create mode 100644 contrib/zstd/wrapper/wrapper_zstd.cc create mode 100644 contrib/zstd/wrapper/wrapper_zstd.h diff --git a/contrib/zstd/CMakeLists.txt b/contrib/zstd/CMakeLists.txt index bae244f..0062e49 100644 --- a/contrib/zstd/CMakeLists.txt +++ b/contrib/zstd/CMakeLists.txt @@ -34,6 +34,8 @@ FetchContent_Declare(libzstd FetchContent_MakeAvailable(libzstd) set(libzstd_INCLUDE_DIR "${libzstd_SOURCE_DIR}/lib") +add_subdirectory(wrapper) + add_sapi_library( sapi_zstd @@ -70,10 +72,16 @@ add_sapi_library( ZSTD_getFrameContentSize + ZSTD_compress_fd + ZSTD_compressStream_fd + + ZSTD_decompress_fd + ZSTD_decompressStream_fd INPUTS ${libzstd_INCLUDE_DIR}/zstd.h + wrapper/wrapper_zstd.h - LIBRARY libzstd_static + LIBRARY wrapper_zstd LIBRARY_NAME Zstd NAMESPACE "" ) diff --git a/contrib/zstd/example/main.cc b/contrib/zstd/example/main.cc index 5c60b9c..3eaa1de 100644 --- a/contrib/zstd/example/main.cc +++ b/contrib/zstd/example/main.cc @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include @@ -25,10 +26,58 @@ #include "contrib/zstd/sandboxed.h" #include "contrib/zstd/utils/utils_zstd.h" +ABSL_FLAG(bool, stream, false, "stream data to sandbox"); ABSL_FLAG(bool, decompress, false, "decompress"); ABSL_FLAG(bool, memory_mode, false, "in memory operations"); ABSL_FLAG(uint32_t, level, 0, "compression level"); +absl::Status Stream(ZstdApi& api, std::string infile_s, std::string outfile_s) { + std::ifstream infile(infile_s, std::ios::binary); + if (!infile.is_open()) { + return absl::UnavailableError(absl::StrCat("Unable to open ", infile_s)); + } + std::ofstream outfile(outfile_s, std::ios::binary); + if (!outfile.is_open()) { + return absl::UnavailableError(absl::StrCat("Unable to open ", outfile_s)); + } + + if (absl::GetFlag(FLAGS_memory_mode) && absl::GetFlag(FLAGS_decompress)) { + return DecompressInMemory(api, infile, outfile); + } + if (absl::GetFlag(FLAGS_memory_mode) && !absl::GetFlag(FLAGS_decompress)) { + return CompressInMemory(api, infile, outfile, absl::GetFlag(FLAGS_level)); + } + if (!absl::GetFlag(FLAGS_memory_mode) && absl::GetFlag(FLAGS_decompress)) { + return DecompressStream(api, infile, outfile); + } + + return CompressStream(api, infile, outfile, absl::GetFlag(FLAGS_level)); +} + +absl::Status FileDescriptor(ZstdApi& api, std::string infile_s, + std::string outfile_s) { + sapi::v::Fd infd(open(infile_s.c_str(), O_RDONLY)); + if (infd.GetValue() < 0) { + return absl::UnavailableError(absl::StrCat("Unable to open ", infile_s)); + } + sapi::v::Fd outfd(open(outfile_s.c_str(), O_WRONLY | O_CREAT)); + if (outfd.GetValue() < 0) { + return absl::UnavailableError(absl::StrCat("Unable to open ", outfile_s)); + } + + if (absl::GetFlag(FLAGS_memory_mode) && absl::GetFlag(FLAGS_decompress)) { + return DecompressInMemoryFD(api, infd, outfd); + } + if (absl::GetFlag(FLAGS_memory_mode) && !absl::GetFlag(FLAGS_decompress)) { + return CompressInMemoryFD(api, infd, outfd, absl::GetFlag(FLAGS_level)); + } + if (!absl::GetFlag(FLAGS_memory_mode) && absl::GetFlag(FLAGS_decompress)) { + return DecompressStreamFD(api, infd, outfd); + } + + return CompressStreamFD(api, infd, outfd, absl::GetFlag(FLAGS_level)); +} + int main(int argc, char* argv[]) { std::string prog_name(argv[0]); google::InitGoogleLogging(argv[0]); @@ -39,17 +88,6 @@ int main(int argc, char* argv[]) { return EXIT_FAILURE; } - std::ifstream infile(args[1], std::ios::binary); - if (!infile.is_open()) { - std::cerr << "Unable to open " << args[1] << std::endl; - return EXIT_FAILURE; - } - std::ofstream outfile(args[2], std::ios::binary); - if (!outfile.is_open()) { - std::cerr << "Unable to open " << args[2] << std::endl; - return EXIT_FAILURE; - } - ZstdSapiSandbox sandbox; if (!sandbox.Init().ok()) { std::cerr << "Unable to start sandbox\n"; @@ -59,16 +97,10 @@ int main(int argc, char* argv[]) { ZstdApi api(&sandbox); absl::Status status; - if (absl::GetFlag(FLAGS_memory_mode) && absl::GetFlag(FLAGS_decompress)) { - status = DecompressInMemory(api, infile, outfile); - } else if (absl::GetFlag(FLAGS_memory_mode) && - !absl::GetFlag(FLAGS_decompress)) { - status = CompressInMemory(api, infile, outfile, absl::GetFlag(FLAGS_level)); - } else if (!absl::GetFlag(FLAGS_memory_mode) && - absl::GetFlag(FLAGS_decompress)) { - status = DecompressStream(api, infile, outfile); + if (absl::GetFlag(FLAGS_stream)) { + status = Stream(api, argv[1], argv[2]); } else { - status = CompressStream(api, infile, outfile, absl::GetFlag(FLAGS_level)); + status = FileDescriptor(api, argv[1], argv[2]); } if (!status.ok()) { diff --git a/contrib/zstd/sandboxed.h b/contrib/zstd/sandboxed.h index 31b0b81..11182e3 100644 --- a/contrib/zstd/sandboxed.h +++ b/contrib/zstd/sandboxed.h @@ -27,10 +27,14 @@ class ZstdSapiSandbox : public ZstdSandbox { std::unique_ptr ModifyPolicy( sandbox2::PolicyBuilder*) override { return sandbox2::PolicyBuilder() + .AllowDynamicStartup() .AllowRead() .AllowWrite() .AllowSystemMalloc() .AllowExit() + .AllowSyscalls({ + __NR_recvmsg + }) .BuildOrDie(); } }; diff --git a/contrib/zstd/test/zstd_test.cc b/contrib/zstd/test/zstd_test.cc index b997574..6a3d5ce 100644 --- a/contrib/zstd/test/zstd_test.cc +++ b/contrib/zstd/test/zstd_test.cc @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include +#include + #include #include @@ -278,8 +281,239 @@ TEST(SandboxTest, CheckCompressAndDecompressStream) { ASSERT_TRUE(outfile.is_open()); status = DecompressStream(api, inmiddle, outfile); + ASSERT_THAT(status, IsOk()) << "Unable to decompress"; + + ASSERT_TRUE(CompareFiles(infile_s, outfile_s)); +} + +TEST(SandboxTest, CheckCompressInMemoryFD) { + ZstdSapiSandbox sandbox; + ASSERT_THAT(sandbox.Init(), IsOk()) << "Couldn't initialize Sandboxed API"; + ZstdApi api = ZstdApi(&sandbox); + + std::string infile_s = GetTestFilePath("text"); + + absl::StatusOr path = + sapi::CreateNamedTempFileAndClose("out.zstd"); + ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + std::string outfile_s = + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + + sapi::v::Fd infd(open(infile_s.c_str(), O_RDONLY)); + ASSERT_GE(infd.GetValue(), 0); + sapi::v::Fd outfd(open(outfile_s.c_str(), O_WRONLY)); + ASSERT_GE(outfd.GetValue(), 0); + + absl::Status status = CompressInMemoryFD(api, infd, outfd, 0); + ASSERT_THAT(status, IsOk()) << "Unable to compress file in memory"; + + off_t inpos = lseek(infd.GetValue(), 0, SEEK_END); + EXPECT_GE(inpos, 0); + off_t outpos = lseek(outfd.GetValue(), 0, SEEK_END); + EXPECT_GE(outpos, 0); + + EXPECT_LT(outpos, inpos); +} + +TEST(SandboxTest, CheckDecompressInMemoryFD) { + ZstdSapiSandbox sandbox; + ASSERT_THAT(sandbox.Init(), IsOk()) << "Couldn't initialize Sandboxed API"; + ZstdApi api = ZstdApi(&sandbox); + + std::string infile_s = GetTestFilePath("text.blob.zstd"); + sapi::v::Fd infd(open(infile_s.c_str(), O_RDONLY)); + ASSERT_GE(infd.GetValue(), 0); + + absl::StatusOr path = sapi::CreateNamedTempFileAndClose("out"); + ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + std::string outfile_s = + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + sapi::v::Fd outfd(open(outfile_s.c_str(), O_WRONLY)); + ASSERT_GE(outfd.GetValue(), 0); + + absl::Status status = DecompressInMemoryFD(api, infd, outfd); + ASSERT_THAT(status, IsOk()) << "Unable to compress file in memory"; + + off_t inpos = lseek(infd.GetValue(), 0, SEEK_END); + EXPECT_GE(inpos, 0); + + off_t outpos = lseek(outfd.GetValue(), 0, SEEK_END); + EXPECT_GE(outpos, 0); + + EXPECT_GT(outpos, inpos); + + ASSERT_TRUE(CompareFiles(GetTestFilePath("text"), outfile_s)); +} + +TEST(SandboxTest, CheckCompressAndDecompressInMemoryFD) { + ZstdSapiSandbox sandbox; + absl::Status status; + int ret; + ASSERT_THAT(sandbox.Init(), IsOk()) << "Couldn't initialize Sandboxed API"; + ZstdApi api = ZstdApi(&sandbox); + + std::string infile_s = GetTestFilePath("text"); + + absl::StatusOr path_middle = + sapi::CreateNamedTempFileAndClose("middle.zstd"); + ASSERT_THAT(path_middle, IsOk()) << "Could not create temp output file"; + std::string middle_s = + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path_middle); + + absl::StatusOr path = sapi::CreateNamedTempFileAndClose("out"); + ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + std::string outfile_s = + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + + sapi::v::Fd infd(open(infile_s.c_str(), O_RDONLY)); + ASSERT_GE(infd.GetValue(), 0); + + sapi::v::Fd outmiddlefd(open(middle_s.c_str(), O_WRONLY)); + ASSERT_GE(outmiddlefd.GetValue(), 0); + + status = CompressInMemoryFD(api, infd, outmiddlefd, 0); + ASSERT_THAT(status, IsOk()) << "Unable to compress file in memory"; + + off_t inpos = lseek(infd.GetValue(), 0, SEEK_END); + EXPECT_GE(inpos, 0); + + off_t outpos = lseek(outmiddlefd.GetValue(), 0, SEEK_END); + EXPECT_GE(outpos, 0); + + EXPECT_LT(outpos, inpos); + + infd.CloseLocalFd(); + outmiddlefd.CloseLocalFd(); + + sapi::v::Fd inmiddlefd(open(middle_s.c_str(), O_RDONLY)); + ASSERT_GE(inmiddlefd.GetValue(), 0); + + sapi::v::Fd outfd(open(outfile_s.c_str(), O_WRONLY)); + ASSERT_GE(outfd.GetValue(), 0); + + status = DecompressInMemoryFD(api, inmiddlefd, outfd); ASSERT_THAT(status, IsOk()) << "Unable to decompress file in memory"; + outfd.CloseLocalFd(); + inmiddlefd.CloseLocalFd(); + + ASSERT_TRUE(CompareFiles(infile_s, outfile_s)); +} + +TEST(SandboxTest, CheckCompressStreamFD) { + absl::Status status; + ZstdSapiSandbox sandbox; + ASSERT_THAT(sandbox.Init(), IsOk()) << "Couldn't initialize Sandboxed API"; + ZstdApi api = ZstdApi(&sandbox); + + std::string infile_s = GetTestFilePath("text"); + + absl::StatusOr path = + sapi::CreateNamedTempFileAndClose("out.zstd"); + ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + std::string outfile_s = + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + + sapi::v::Fd infd(open(infile_s.c_str(), O_RDONLY)); + ASSERT_GE(infd.GetValue(), 0); + + sapi::v::Fd outfd(open(outfile_s.c_str(), O_WRONLY)); + ASSERT_GE(outfd.GetValue(), 0); + + status = CompressStreamFD(api, infd, outfd, 0); + ASSERT_THAT(status, IsOk()) << "Unable to compress stream"; + + off_t inpos = lseek(infd.GetValue(), 0, SEEK_END); + EXPECT_GE(inpos, 0); + + off_t outpos = lseek(outfd.GetValue(), 0, SEEK_END); + EXPECT_GE(outpos, 0); + + EXPECT_LT(outpos, inpos); +} + +TEST(SandboxTest, CheckDecompressStreamFD) { + absl::Status status; + ZstdSapiSandbox sandbox; + ASSERT_THAT(sandbox.Init(), IsOk()) << "Couldn't initialize Sandboxed API"; + ZstdApi api = ZstdApi(&sandbox); + + std::string infile_s = GetTestFilePath("text.stream.zstd"); + + absl::StatusOr path = sapi::CreateNamedTempFileAndClose("out"); + ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + std::string outfile_s = + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + + sapi::v::Fd infd(open(infile_s.c_str(), O_RDONLY)); + ASSERT_GE(infd.GetValue(), 0); + + sapi::v::Fd outfd(open(outfile_s.c_str(), O_WRONLY)); + ASSERT_GE(outfd.GetValue(), 0); + + status = DecompressStreamFD(api, infd, outfd); + ASSERT_THAT(status, IsOk()) << "Unable to decompress stream"; + + off_t inpos = lseek(infd.GetValue(), 0, SEEK_END); + EXPECT_GE(inpos, 0); + + off_t outpos = lseek(outfd.GetValue(), 0, SEEK_END); + EXPECT_GE(outpos, 0); + + EXPECT_GT(outpos, inpos); + + ASSERT_TRUE(CompareFiles(GetTestFilePath("text"), outfile_s)); +} + +TEST(SandboxTest, CheckCompressAndDecompressStreamFD) { + ZstdSapiSandbox sandbox; + absl::Status status; + int ret; + ASSERT_THAT(sandbox.Init(), IsOk()) << "Couldn't initialize Sandboxed API"; + ZstdApi api = ZstdApi(&sandbox); + + std::string infile_s = GetTestFilePath("text"); + + absl::StatusOr path_middle = + sapi::CreateNamedTempFileAndClose("middle.zstd"); + ASSERT_THAT(path_middle, IsOk()) << "Could not create temp output file"; + std::string middle_s = + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path_middle); + + absl::StatusOr path = sapi::CreateNamedTempFileAndClose("out"); + ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + std::string outfile_s = + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + + sapi::v::Fd infd(open(infile_s.c_str(), O_RDONLY)); + ASSERT_GE(infd.GetValue(), 0); + + sapi::v::Fd outmiddlefd(open(middle_s.c_str(), O_WRONLY)); + ASSERT_GE(outmiddlefd.GetValue(), 0); + + status = CompressStreamFD(api, infd, outmiddlefd, 0); + ASSERT_THAT(status, IsOk()) << "Unable to compress stream"; + + off_t inpos = lseek(infd.GetValue(), 0, SEEK_END); + EXPECT_GE(inpos, 0); + + off_t outmiddlepos = lseek(outmiddlefd.GetValue(), 0, SEEK_END); + EXPECT_GE(outmiddlepos, 0); + + EXPECT_LT(outmiddlepos, inpos); + + infd.CloseLocalFd(); + outmiddlefd.CloseLocalFd(); + + sapi::v::Fd inmiddlefd(open(middle_s.c_str(), O_RDONLY)); + ASSERT_GE(inmiddlefd.GetValue(), 0); + + sapi::v::Fd outfd(open(outfile_s.c_str(), O_WRONLY)); + ASSERT_GE(outfd.GetValue(), 0); + + status = DecompressStreamFD(api, inmiddlefd, outfd); + ASSERT_THAT(status, IsOk()) << "Unable to decompress stream"; + ASSERT_TRUE(CompareFiles(infile_s, outfile_s)); } diff --git a/contrib/zstd/utils/utils_zstd.cc b/contrib/zstd/utils/utils_zstd.cc index 806b3d0..4f06e60 100644 --- a/contrib/zstd/utils/utils_zstd.cc +++ b/contrib/zstd/utils/utils_zstd.cc @@ -110,7 +110,7 @@ absl::Status CompressStream(ZstdApi& api, std::ifstream& in_stream, } // Create Zstd context. - SAPI_ASSIGN_OR_RETURN(ZSTD_CCtx * cctx, api.ZSTD_createCCtx()); + SAPI_ASSIGN_OR_RETURN(ZSTD_CCtx* cctx, api.ZSTD_createCCtx()); sapi::v::RemotePtr rcctx(cctx); SAPI_ASSIGN_OR_RETURN(iserr, api.ZSTD_CCtx_setParameter( @@ -196,7 +196,7 @@ absl::Status DecompressStream(ZstdApi& api, std::ifstream& in_stream, } // Create Zstd context. - SAPI_ASSIGN_OR_RETURN(ZSTD_DCtx * dctx, api.ZSTD_createDCtx()); + SAPI_ASSIGN_OR_RETURN(ZSTD_DCtx* dctx, api.ZSTD_createDCtx()); sapi::v::RemotePtr rdctx(dctx); // Decompress. @@ -241,3 +241,95 @@ absl::Status DecompressStream(ZstdApi& api, std::ifstream& in_stream, return absl::OkStatus(); } + +absl::Status CompressInMemoryFD(ZstdApi& api, sapi::v::Fd& infd, + sapi::v::Fd& outfd, int level) { + SAPI_RETURN_IF_ERROR(api.GetSandbox()->TransferToSandboxee(&infd)); + SAPI_RETURN_IF_ERROR(api.GetSandbox()->TransferToSandboxee(&outfd)); + + SAPI_ASSIGN_OR_RETURN( + int iserr, + api.ZSTD_compress_fd(infd.GetRemoteFd(), outfd.GetRemoteFd(), 0)); + SAPI_ASSIGN_OR_RETURN(iserr, api.ZSTD_isError(iserr)) + if (iserr) { + return absl::UnavailableError("Unable to compress file"); + } + + infd.CloseRemoteFd(api.GetSandbox()->rpc_channel()).IgnoreError(); + outfd.CloseRemoteFd(api.GetSandbox()->rpc_channel()).IgnoreError(); + + return absl::OkStatus(); +} + +absl::Status DecompressInMemoryFD(ZstdApi& api, sapi::v::Fd& infd, + sapi::v::Fd& outfd) { + SAPI_RETURN_IF_ERROR(api.GetSandbox()->TransferToSandboxee(&infd)); + SAPI_RETURN_IF_ERROR(api.GetSandbox()->TransferToSandboxee(&outfd)); + + SAPI_ASSIGN_OR_RETURN(int iserr, api.ZSTD_decompress_fd(infd.GetRemoteFd(), + outfd.GetRemoteFd())); + SAPI_ASSIGN_OR_RETURN(iserr, api.ZSTD_isError(iserr)) + if (iserr) { + return absl::UnavailableError("Unable to compress file"); + } + + infd.CloseRemoteFd(api.GetSandbox()->rpc_channel()).IgnoreError(); + outfd.CloseRemoteFd(api.GetSandbox()->rpc_channel()).IgnoreError(); + + return absl::OkStatus(); +} + +absl::Status CompressStreamFD(ZstdApi& api, sapi::v::Fd& infd, + sapi::v::Fd& outfd, int level) { + SAPI_ASSIGN_OR_RETURN(ZSTD_CCtx* cctx, api.ZSTD_createCCtx()); + sapi::v::RemotePtr rcctx(cctx); + + int iserr; + SAPI_ASSIGN_OR_RETURN(iserr, api.ZSTD_CCtx_setParameter( + &rcctx, ZSTD_c_compressionLevel, level)); + SAPI_ASSIGN_OR_RETURN(iserr, api.ZSTD_isError(iserr)); + if (iserr) { + return absl::UnavailableError("Unable to set parameter l"); + } + SAPI_ASSIGN_OR_RETURN( + iserr, api.ZSTD_CCtx_setParameter(&rcctx, ZSTD_c_checksumFlag, 1)); + SAPI_ASSIGN_OR_RETURN(iserr, api.ZSTD_isError(iserr)); + if (iserr) { + return absl::UnavailableError("Unable to set parameter c"); + } + + SAPI_RETURN_IF_ERROR(api.GetSandbox()->TransferToSandboxee(&infd)); + SAPI_RETURN_IF_ERROR(api.GetSandbox()->TransferToSandboxee(&outfd)); + + SAPI_ASSIGN_OR_RETURN(iserr, + api.ZSTD_compressStream_fd(&rcctx, infd.GetRemoteFd(), + outfd.GetRemoteFd())); + if (iserr) { + return absl::UnavailableError("Unable to compress"); + } + + infd.CloseRemoteFd(api.GetSandbox()->rpc_channel()).IgnoreError(); + outfd.CloseRemoteFd(api.GetSandbox()->rpc_channel()).IgnoreError(); + + return absl::OkStatus(); +} + +absl::Status DecompressStreamFD(ZstdApi& api, sapi::v::Fd& infd, + sapi::v::Fd& outfd) { + SAPI_ASSIGN_OR_RETURN(ZSTD_DCtx* dctx, api.ZSTD_createDCtx()); + sapi::v::RemotePtr rdctx(dctx); + + SAPI_RETURN_IF_ERROR(api.GetSandbox()->TransferToSandboxee(&infd)); + SAPI_RETURN_IF_ERROR(api.GetSandbox()->TransferToSandboxee(&outfd)); + + SAPI_ASSIGN_OR_RETURN(int iserr, + api.ZSTD_decompressStream_fd(&rdctx, infd.GetRemoteFd(), + outfd.GetRemoteFd())); + if (iserr) { + return absl::UnavailableError("Unable to decompress"); + } + infd.CloseRemoteFd(api.GetSandbox()->rpc_channel()).IgnoreError(); + outfd.CloseRemoteFd(api.GetSandbox()->rpc_channel()).IgnoreError(); + + return absl::OkStatus(); +} diff --git a/contrib/zstd/utils/utils_zstd.h b/contrib/zstd/utils/utils_zstd.h index cac7a9d..02e3500 100644 --- a/contrib/zstd/utils/utils_zstd.h +++ b/contrib/zstd/utils/utils_zstd.h @@ -24,10 +24,18 @@ absl::Status CompressInMemory(ZstdApi& api, std::ifstream& in_stream, std::ofstream& out_stream, int level); absl::Status DecompressInMemory(ZstdApi& api, std::ifstream& in_stream, std::ofstream& out_stream); +absl::Status CompressInMemoryFD(ZstdApi& api, sapi::v::Fd& infd, + sapi::v::Fd& outfd, int level); +absl::Status DecompressInMemoryFD(ZstdApi& api, sapi::v::Fd& infd, + sapi::v::Fd& outfd); absl::Status CompressStream(ZstdApi& api, std::ifstream& in_stream, std::ofstream& out_stream, int level); absl::Status DecompressStream(ZstdApi& api, std::ifstream& in_stream, std::ofstream& out_stream); +absl::Status CompressStreamFD(ZstdApi& api, sapi::v::Fd& infd, + sapi::v::Fd& outfd, int level); +absl::Status DecompressStreamFD(ZstdApi& api, sapi::v::Fd& infd, + sapi::v::Fd& outfd); #endif // CONTRIB_ZSTD_UTILS_UTILS_ZSTD_H_ diff --git a/contrib/zstd/wrapper/CMakeLists.txt b/contrib/zstd/wrapper/CMakeLists.txt new file mode 100644 index 0000000..ff322ea --- /dev/null +++ b/contrib/zstd/wrapper/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright 2022 Google LLC +# +# 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 +# +# https://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. + +add_library( + wrapper_zstd STATIC + + wrapper_zstd.cc +) + +target_link_libraries(wrapper_zstd PUBLIC + libzstd_static +) + +target_include_directories(wrapper_zstd PUBLIC + ${libzstd_INCLUDE_DIR} +) diff --git a/contrib/zstd/wrapper/wrapper_zstd.cc b/contrib/zstd/wrapper/wrapper_zstd.cc new file mode 100644 index 0000000..020053a --- /dev/null +++ b/contrib/zstd/wrapper/wrapper_zstd.cc @@ -0,0 +1,184 @@ +// Copyright 2022 Google LLC +// +// 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 +// +// https://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 "wrapper_zstd.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "zstd.h" + +static constexpr size_t kFileMaxSize = 1024 * 1024 * 1024; // 1GB + +off_t FDGetSize(int fd) { + off_t size = lseek(fd, 0, SEEK_END); + if (size < 0) { + return -1; + } + if (lseek(fd, 0, SEEK_SET) < 0) { + return -1; + } + + return size; +} + +int ZSTD_compress_fd(int fdin, int fdout, int level) { + off_t sizein = FDGetSize(fdin); + if (sizein <= 0) { + return -1; + } + + size_t sizeout = ZSTD_compressBound(sizein); + + auto bufin = std::make_unique(sizein); + auto bufout = std::make_unique(sizeout); + + if (read(fdin, bufin.get(), sizein) != sizein) { + return -1; + } + + int retsize = + ZSTD_compress(bufout.get(), sizeout, bufin.get(), sizein, level); + if (ZSTD_isError(retsize)) { + return -1; + } + + if (write(fdout, bufout.get(), retsize) != retsize) { + return -1; + } + + return 0; +} + +int ZSTD_compressStream_fd(ZSTD_CCtx* cctx, int fdin, int fdout) { + size_t sizein = ZSTD_CStreamInSize(); + size_t sizeout = ZSTD_CStreamOutSize(); + + auto bufin = std::make_unique(sizein); + auto bufout = std::make_unique(sizeout); + + ssize_t size; + while ((size = read(fdin, bufin.get(), sizein)) > 0) { + ZSTD_inBuffer_s struct_in; + struct_in.src = bufin.get(); + struct_in.pos = 0; + struct_in.size = size; + + ZSTD_EndDirective mode = ZSTD_e_continue; + if (size < sizein) { + mode = ZSTD_e_end; + } + + bool isdone = false; + while (!isdone) { + ZSTD_outBuffer_s struct_out; + struct_out.dst = bufout.get(); + struct_out.pos = 0; + struct_out.size = sizeout; + + size_t remaining = + ZSTD_compressStream2(cctx, &struct_out, &struct_in, mode); + if (ZSTD_isError(remaining)) { + return -1; + } + if (write(fdout, bufout.get(), struct_out.pos) != struct_out.pos) { + return -1; + } + + if (mode == ZSTD_e_continue) { + isdone = (struct_in.pos == size); + } else { + isdone = (remaining == 0); + } + } + } + + if (size != 0) { + return -1; + } + + return 0; +} + +int ZSTD_decompress_fd(int fdin, int fdout) { + off_t sizein = FDGetSize(fdin); + if (sizein <= 0) { + return -1; + } + auto bufin = std::make_unique(sizein); + if (read(fdin, bufin.get(), sizein) != sizein) { + return -1; + } + + size_t sizeout = ZSTD_getFrameContentSize(bufin.get(), sizein); + if (ZSTD_isError(sizeout) || sizeout > kFileMaxSize) { + return -1; + } + + auto bufout = std::make_unique(sizeout); + + size_t desize = ZSTD_decompress(bufout.get(), sizeout, bufin.get(), sizein); + if (ZSTD_isError(desize) || desize != sizeout) { + return -1; + } + + if (write(fdout, bufout.get(), sizeout) != sizeout) { + return -1; + } + + return 0; +} + +int ZSTD_decompressStream_fd(ZSTD_DCtx* dctx, int fdin, int fdout) { + size_t sizein = ZSTD_CStreamInSize(); + size_t sizeout = ZSTD_CStreamOutSize(); + + auto bufin = std::make_unique(sizein); + auto bufout = std::make_unique(sizeout); + + ssize_t size; + while ((size = read(fdin, bufin.get(), sizein)) > 0) { + ZSTD_inBuffer_s struct_in; + struct_in.src = bufin.get(); + struct_in.pos = 0; + struct_in.size = size; + + while (struct_in.pos < size) { + ZSTD_outBuffer_s struct_out; + struct_out.dst = bufout.get(); + struct_out.pos = 0; + struct_out.size = sizeout; + + size_t ret = ZSTD_decompressStream(dctx, &struct_out, &struct_in); + if (ZSTD_isError(ret)) { + return -1; + } + if (write(fdout, bufout.get(), struct_out.pos) != struct_out.pos) { + return -1; + } + } + } + + if (size != 0) { + return -1; + } + + return 0; +} diff --git a/contrib/zstd/wrapper/wrapper_zstd.h b/contrib/zstd/wrapper/wrapper_zstd.h new file mode 100644 index 0000000..60d0fa2 --- /dev/null +++ b/contrib/zstd/wrapper/wrapper_zstd.h @@ -0,0 +1,28 @@ +// Copyright 2022 Google LLC +// +// 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 +// +// https://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 CONTRIB_ZSTD_WRAPPER_WRAPPER_ZSTD_H_ +#define CONTRIB_ZSTD_WRAPPER_WRAPPER_ZSTD_H_ + +#include "zstd.h" + +extern "C" { +int ZSTD_compress_fd(int fdin, int fdout, int level); +int ZSTD_compressStream_fd(ZSTD_CCtx* cctx, int fdin, int fdout); + +int ZSTD_decompress_fd(int fdin, int fdout); +int ZSTD_decompressStream_fd(ZSTD_DCtx* dctx, int fdin, int fdout); +}; + +#endif // CONTRIB_ZSTD_WRAPPER_WRAPPER_ZSTD_H_ From 44088bb7d580280a9fb15eaa02659e08f8c4f485 Mon Sep 17 00:00:00 2001 From: Mariusz Zaborski Date: Thu, 3 Feb 2022 13:27:15 -0500 Subject: [PATCH 03/23] ZStandard: Fix the order of headers --- contrib/zstd/test/zstd_test.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/zstd/test/zstd_test.cc b/contrib/zstd/test/zstd_test.cc index 6a3d5ce..11989a3 100644 --- a/contrib/zstd/test/zstd_test.cc +++ b/contrib/zstd/test/zstd_test.cc @@ -18,10 +18,10 @@ #include #include -#include "gmock/gmock.h" -#include "gtest/gtest.h" #include "contrib/zstd/sandboxed.h" #include "contrib/zstd/utils/utils_zstd.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" #include "sandboxed_api/util/path.h" #include "sandboxed_api/util/status_matchers.h" #include "sandboxed_api/util/temp_file.h" From e3da4da69ba2f5b5200c8204a835a4a18e4475cf Mon Sep 17 00:00:00 2001 From: Mariusz Zaborski Date: Thu, 3 Feb 2022 13:41:32 -0500 Subject: [PATCH 04/23] ZStandard: Simplify creation of paths --- contrib/zstd/test/zstd_test.cc | 106 +++++++++++++++------------------ 1 file changed, 48 insertions(+), 58 deletions(-) diff --git a/contrib/zstd/test/zstd_test.cc b/contrib/zstd/test/zstd_test.cc index 11989a3..e9730a1 100644 --- a/contrib/zstd/test/zstd_test.cc +++ b/contrib/zstd/test/zstd_test.cc @@ -104,11 +104,10 @@ TEST(SandboxTest, CheckCompressInMemory) { std::string infile_s = GetTestFilePath("text"); - absl::StatusOr path = - sapi::CreateNamedTempFileAndClose("out.zstd"); - ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + SAPI_ASSERT_OK_AND_ASSIGN(std::string path, + sapi::CreateNamedTempFileAndClose("out.zstd")); std::string outfile_s = - sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), path); std::ifstream infile(infile_s, std::ios::binary); ASSERT_TRUE(infile.is_open()); @@ -129,10 +128,10 @@ TEST(SandboxTest, CheckDecompressInMemory) { std::string infile_s = GetTestFilePath("text.blob.zstd"); - absl::StatusOr path = sapi::CreateNamedTempFileAndClose("out"); - ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + SAPI_ASSERT_OK_AND_ASSIGN(std::string path, + sapi::CreateNamedTempFileAndClose("out")); std::string outfile_s = - sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), path); std::ifstream infile(infile_s, std::ios::binary); ASSERT_TRUE(infile.is_open()); @@ -156,16 +155,15 @@ TEST(SandboxTest, CheckCompressAndDecompressInMemory) { std::string infile_s = GetTestFilePath("text"); - absl::StatusOr path_middle = - sapi::CreateNamedTempFileAndClose("middle.zstd"); - ASSERT_THAT(path_middle, IsOk()) << "Could not create temp output file"; + SAPI_ASSERT_OK_AND_ASSIGN(std::string path_middle, + sapi::CreateNamedTempFileAndClose("middle.zstd")); std::string middle_s = - sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path_middle); + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), path_middle); - absl::StatusOr path = sapi::CreateNamedTempFileAndClose("out"); - ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + SAPI_ASSERT_OK_AND_ASSIGN(std::string path, + sapi::CreateNamedTempFileAndClose("out")); std::string outfile_s = - sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), path); std::ifstream infile(infile_s, std::ios::binary); ASSERT_TRUE(infile.is_open()); @@ -196,12 +194,10 @@ TEST(SandboxTest, CheckCompressStream) { ZstdApi api = ZstdApi(&sandbox); std::string infile_s = GetTestFilePath("text"); - - absl::StatusOr path = - sapi::CreateNamedTempFileAndClose("out.zstd"); - ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + SAPI_ASSERT_OK_AND_ASSIGN(std::string path, + sapi::CreateNamedTempFileAndClose("out.zstd")); std::string outfile_s = - sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), path); std::ifstream infile(infile_s, std::ios::binary); ASSERT_TRUE(infile.is_open()); @@ -223,11 +219,10 @@ TEST(SandboxTest, CheckDecompressStream) { ZstdApi api = ZstdApi(&sandbox); std::string infile_s = GetTestFilePath("text.stream.zstd"); - - absl::StatusOr path = sapi::CreateNamedTempFileAndClose("out"); - ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + SAPI_ASSERT_OK_AND_ASSIGN(std::string path, + sapi::CreateNamedTempFileAndClose("out")); std::string outfile_s = - sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), path); std::ifstream infile(infile_s, std::ios::binary); ASSERT_TRUE(infile.is_open()); @@ -251,16 +246,15 @@ TEST(SandboxTest, CheckCompressAndDecompressStream) { std::string infile_s = GetTestFilePath("text"); - absl::StatusOr path_middle = - sapi::CreateNamedTempFileAndClose("middle.zstd"); - ASSERT_THAT(path_middle, IsOk()) << "Could not create temp output file"; + SAPI_ASSERT_OK_AND_ASSIGN(std::string path_middle, + sapi::CreateNamedTempFileAndClose("middle.zstd")); std::string middle_s = - sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path_middle); + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), path_middle); - absl::StatusOr path = sapi::CreateNamedTempFileAndClose("out"); - ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + SAPI_ASSERT_OK_AND_ASSIGN(std::string path, + sapi::CreateNamedTempFileAndClose("out")); std::string outfile_s = - sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), path); std::ifstream infile(infile_s, std::ios::binary); ASSERT_TRUE(infile.is_open()); @@ -293,11 +287,10 @@ TEST(SandboxTest, CheckCompressInMemoryFD) { std::string infile_s = GetTestFilePath("text"); - absl::StatusOr path = - sapi::CreateNamedTempFileAndClose("out.zstd"); - ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + SAPI_ASSERT_OK_AND_ASSIGN(std::string path, + sapi::CreateNamedTempFileAndClose("out.zstd")); std::string outfile_s = - sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), path); sapi::v::Fd infd(open(infile_s.c_str(), O_RDONLY)); ASSERT_GE(infd.GetValue(), 0); @@ -324,10 +317,10 @@ TEST(SandboxTest, CheckDecompressInMemoryFD) { sapi::v::Fd infd(open(infile_s.c_str(), O_RDONLY)); ASSERT_GE(infd.GetValue(), 0); - absl::StatusOr path = sapi::CreateNamedTempFileAndClose("out"); - ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + SAPI_ASSERT_OK_AND_ASSIGN(std::string path, + sapi::CreateNamedTempFileAndClose("out")); std::string outfile_s = - sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), path); sapi::v::Fd outfd(open(outfile_s.c_str(), O_WRONLY)); ASSERT_GE(outfd.GetValue(), 0); @@ -354,16 +347,15 @@ TEST(SandboxTest, CheckCompressAndDecompressInMemoryFD) { std::string infile_s = GetTestFilePath("text"); - absl::StatusOr path_middle = - sapi::CreateNamedTempFileAndClose("middle.zstd"); - ASSERT_THAT(path_middle, IsOk()) << "Could not create temp output file"; + SAPI_ASSERT_OK_AND_ASSIGN(std::string path_middle, + sapi::CreateNamedTempFileAndClose("middle.zstd")); std::string middle_s = - sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path_middle); + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), path_middle); - absl::StatusOr path = sapi::CreateNamedTempFileAndClose("out"); - ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + SAPI_ASSERT_OK_AND_ASSIGN(std::string path, + sapi::CreateNamedTempFileAndClose("out")); std::string outfile_s = - sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), path); sapi::v::Fd infd(open(infile_s.c_str(), O_RDONLY)); ASSERT_GE(infd.GetValue(), 0); @@ -408,11 +400,10 @@ TEST(SandboxTest, CheckCompressStreamFD) { std::string infile_s = GetTestFilePath("text"); - absl::StatusOr path = - sapi::CreateNamedTempFileAndClose("out.zstd"); - ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + SAPI_ASSERT_OK_AND_ASSIGN(std::string path, + sapi::CreateNamedTempFileAndClose("out.zstd")); std::string outfile_s = - sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), path); sapi::v::Fd infd(open(infile_s.c_str(), O_RDONLY)); ASSERT_GE(infd.GetValue(), 0); @@ -440,10 +431,10 @@ TEST(SandboxTest, CheckDecompressStreamFD) { std::string infile_s = GetTestFilePath("text.stream.zstd"); - absl::StatusOr path = sapi::CreateNamedTempFileAndClose("out"); - ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + SAPI_ASSERT_OK_AND_ASSIGN(std::string path, + sapi::CreateNamedTempFileAndClose("out")); std::string outfile_s = - sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), path); sapi::v::Fd infd(open(infile_s.c_str(), O_RDONLY)); ASSERT_GE(infd.GetValue(), 0); @@ -474,16 +465,15 @@ TEST(SandboxTest, CheckCompressAndDecompressStreamFD) { std::string infile_s = GetTestFilePath("text"); - absl::StatusOr path_middle = - sapi::CreateNamedTempFileAndClose("middle.zstd"); - ASSERT_THAT(path_middle, IsOk()) << "Could not create temp output file"; + SAPI_ASSERT_OK_AND_ASSIGN(std::string path_middle, + sapi::CreateNamedTempFileAndClose("middle.zstd")); std::string middle_s = - sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path_middle); + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), path_middle); - absl::StatusOr path = sapi::CreateNamedTempFileAndClose("out"); - ASSERT_THAT(path, IsOk()) << "Could not create temp output file"; + SAPI_ASSERT_OK_AND_ASSIGN(std::string path, + sapi::CreateNamedTempFileAndClose("out")); std::string outfile_s = - sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), *path); + sapi::file::JoinPath(sapi::file_util::fileops::GetCWD(), path); sapi::v::Fd infd(open(infile_s.c_str(), O_RDONLY)); ASSERT_GE(infd.GetValue(), 0); From 1beba0b3add5d41c32f8f455b3a4e386156b6164 Mon Sep 17 00:00:00 2001 From: Mariusz Zaborski Date: Tue, 8 Feb 2022 13:02:52 -0500 Subject: [PATCH 05/23] ZStandard: use long includes --- contrib/zstd/wrapper/CMakeLists.txt | 1 + contrib/zstd/wrapper/wrapper_zstd.cc | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/zstd/wrapper/CMakeLists.txt b/contrib/zstd/wrapper/CMakeLists.txt index ff322ea..f13b4e4 100644 --- a/contrib/zstd/wrapper/CMakeLists.txt +++ b/contrib/zstd/wrapper/CMakeLists.txt @@ -23,5 +23,6 @@ target_link_libraries(wrapper_zstd PUBLIC ) target_include_directories(wrapper_zstd PUBLIC + ${SAPI_SOURCE_DIR} ${libzstd_INCLUDE_DIR} ) diff --git a/contrib/zstd/wrapper/wrapper_zstd.cc b/contrib/zstd/wrapper/wrapper_zstd.cc index 020053a..999f24a 100644 --- a/contrib/zstd/wrapper/wrapper_zstd.cc +++ b/contrib/zstd/wrapper/wrapper_zstd.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "wrapper_zstd.h" +#include "contrib/zstd/wrapper/wrapper_zstd.h" #include #include From 7e5a398164f254a360e2ae7001819fc0972a6863 Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Wed, 9 Feb 2022 05:19:56 -0800 Subject: [PATCH 06/23] Migrate the pffft sandbox to `contrib/` PiperOrigin-RevId: 427443359 Change-Id: I852a818ae302a86abe32a2820f349f67861e342e --- contrib/CMakeLists.txt | 1 + contrib/README.md | 13 ++-- .../pffft/CMakeLists.txt | 63 +++++++++---------- .../pffft/README.md | 38 ++++++++--- .../pffft/main_pffft_sandboxed.cc | 3 +- oss-internship-2020/pffft/.gitignore | 3 - 6 files changed, 71 insertions(+), 50 deletions(-) rename {oss-internship-2020 => contrib}/pffft/CMakeLists.txt (63%) rename {oss-internship-2020 => contrib}/pffft/README.md (76%) rename {oss-internship-2020 => contrib}/pffft/main_pffft_sandboxed.cc (99%) delete mode 100644 oss-internship-2020/pffft/.gitignore diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index 7df8fcc..42c2b14 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -16,6 +16,7 @@ set(SAPI_CONTRIB_SANDBOXES hunspell jsonnet + pffft zopfli zstd ) diff --git a/contrib/README.md b/contrib/README.md index c9ae961..9853cda 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -5,12 +5,13 @@ libraries. ## Projects Sandboxed -Directory | Project | Home Page | Integration ------------ | ------------------------------------------------- | -------------------------------------------------------------------- | ----------- -`jsonnet/` | Jsonnet - The Data Templating Language | [github.com/google/jsonnet](https://github.com/google/jsonnet) | CMake -`hunspell/` | Hunspell - The most popular spellchecking library | [github.com/hunspell/hunspell](https://github.com/hunspell/hunspell) | CMake -`zopfli` | Zopfli - Compression Algorithm | [github.com/google/zopfli](https://github.com/google/zopfli) | CMake -`zstd/` | Zstandard - Fast real-time compression algorithm | [github.com/facebook/zstd](https://github.com/facebook/zstd) | CMake +Directory | Project | Home Page | Integration +----------- | ------------------------------------------------- | ---------------------------------------------------------------------------- | ----------- +`hunspell/` | Hunspell - The most popular spellchecking library | [github.com/hunspell/hunspell](https://github.com/hunspell/hunspell) | CMake +`jsonnet/` | Jsonnet - The Data Templating Language | [github.com/google/jsonnet](https://github.com/google/jsonnet) | CMake +`pffft/` | PFFFT - a pretty fast Fourier Transform | [bitbucket.org/jpommier/pffft.git](https://bitbucket.org/jpommier/pffft.git) | CMake +`zopfli` | Zopfli - Compression Algorithm | [github.com/google/zopfli](https://github.com/google/zopfli) | CMake +`zstd/` | Zstandard - Fast real-time compression algorithm | [github.com/facebook/zstd](https://github.com/facebook/zstd) | CMake ## Projects Shipping with Sandboxed API Sandboxes diff --git a/oss-internship-2020/pffft/CMakeLists.txt b/contrib/pffft/CMakeLists.txt similarity index 63% rename from oss-internship-2020/pffft/CMakeLists.txt rename to contrib/pffft/CMakeLists.txt index 5641b62..5b05ef4 100644 --- a/oss-internship-2020/pffft/CMakeLists.txt +++ b/contrib/pffft/CMakeLists.txt @@ -12,50 +12,48 @@ # See the License for the specific language governing permissions and # limitations under the License. -cmake_minimum_required(VERSION 3.10) - +cmake_minimum_required(VERSION 3.13..3.22) project(pffft CXX C) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) +if(NOT TARGET sapi::sapi) + set(SAPI_ROOT "../.." CACHE PATH "Path to the Sandboxed API source tree") + add_subdirectory("${SAPI_ROOT}" + "${CMAKE_BINARY_DIR}/sandboxed-api-build" + EXCLUDE_FROM_ALL) +endif() + +include(CheckLibraryExists) + +FetchContent_Declare(pffft + GIT_REPOSITORY https://bitbucket.org/jpommier/pffft.git + GIT_TAG 988259a41d1522047a9420e6265a6ba8289c1654 # 2021-12-02 +) +FetchContent_MakeAvailable(pffft) + add_library(pffft STATIC - master/pffft.c - master/pffft.h - master/fftpack.c - master/fftpack.h + "${pffft_SOURCE_DIR}/pffft.c" + "${pffft_SOURCE_DIR}/pffft.h" + "${pffft_SOURCE_DIR}/fftpack.c" + "${pffft_SOURCE_DIR}/fftpack.h" ) add_executable(pffft_main - master/test_pffft.c + "${pffft_SOURCE_DIR}/test_pffft.c" ) - target_link_libraries(pffft_main PRIVATE pffft ) -set(MATH_LIBS "") -include(CheckLibraryExists) -check_library_exists(m sin "" LIBM) -if(LIBM) - list(APPEND MATH_LIBS "m") +check_library_exists(m sin "" _sapi_HAVE_LIBM) +if(_sapi_HAVE_LIBM) + target_link_libraries(pffft PUBLIC + m + ) endif() -target_link_libraries(pffft PUBLIC ${MATH_LIBS}) - -# Adding dependencies -set(SAPI_ROOT "../.." CACHE PATH "Path to the Sandboxed API source tree") -# Then configure: -# mkdir -p build && cd build -# cmake .. -G Ninja -DSAPI_ROOT=$HOME/sapi_root - -set(SAPI_ENABLE_EXAMPLES OFF CACHE BOOL "") -set(SAPI_ENABLE_TESTS OFF CACHE BOOL "") -add_subdirectory("${SAPI_ROOT}" - "${CMAKE_BINARY_DIR}/sandboxed-api-build" - # Omit this to have the full Sandboxed API in IDE - EXCLUDE_FROM_ALL) - add_sapi_library(pffft_sapi FUNCTIONS pffft_new_setup pffft_destroy_setup @@ -83,22 +81,23 @@ add_sapi_library(pffft_sapi sinti sint - INPUTS master/pffft.h master/fftpack.h + INPUTS "${pffft_SOURCE_DIR}/pffft.h" + "${pffft_SOURCE_DIR}/fftpack.h" LIBRARY pffft LIBRARY_NAME Pffft NAMESPACE "" ) - +add_library(sapi_contrib::pffft ALIAS pffft_sapi) target_include_directories(pffft_sapi INTERFACE "${PROJECT_BINARY_DIR}" + "${SAPI_SOURCE_DIR}" ) add_executable(pffft_sandboxed main_pffft_sandboxed.cc ) - target_link_libraries(pffft_sandboxed PRIVATE - pffft_sapi + sapi_contrib::pffft sapi::sapi ) diff --git a/oss-internship-2020/pffft/README.md b/contrib/pffft/README.md similarity index 76% rename from oss-internship-2020/pffft/README.md rename to contrib/pffft/README.md index d847b72..88bec66 100644 --- a/oss-internship-2020/pffft/README.md +++ b/contrib/pffft/README.md @@ -1,16 +1,35 @@ # Sandboxing PFFFT library +This library was sandboxed as part of Google's summer 2020 internship program +([blog post](https://security.googleblog.com/2020/12/improving-open-source-security-during.html)). + Build System: CMake OS: Linux -### Check out the PFFFT library & CMake set up -``` -git submodule update --init --recursive +### How to use from an existing Project -mkdir -p build && cd build -cmake .. -G Ninja -DPFFFT_ROOT_DIR=$PWD -ninjas +If your project does not include Sandboxed API as a dependency yet, add the +following lines to the main `CMakeLists.txt`: + +```cmake +include(FetchContent) + +FetchContent_Declare(sandboxed-api + GIT_REPOSITORY https://github.com/google/sandboxed-api + GIT_TAG main # Or pin a specific commit/tag +) +FetchContent_MakeAvailable(sandboxed-api) # CMake 3.14 or higher + +add_sapi_subdirectory(contrib/pffft) ``` + +The `add_sapi_subdirectory()` macro sets up the source and binary directories +for the sandboxed jsonnet targets. + +Afterwards your project's code can link to `sapi_contrib::pffft` and use the +generated header `pffft_sapi.sapi.h`. An example sandbox policy can be found +in `main_pffft_sandboxed.cc`. + ### For testing: `cd build`, then `./pffft_sandboxed` @@ -19,14 +38,15 @@ display custom info with `./pffft_sandboxed --logtostderr` ## ***About the project*** -*PFFFT library is concerned with 1D Fast-Fourier Transformations finding a + +PFFFT library is concerned with 1D Fast-Fourier Transformations finding a compromise between accuracy and speed. It deals with real and complex vectors, both cases being illustrated in the testing part (`test_pffft.c` for initially and original version, `main_pffft_sandboxed.cc` for our currently implemented sandboxed version). The original files can be found at: https://bitbucket.org/jpommier/pffft/src.* -*The purpose of sandboxing is to limit the permissions and capabilities of +The purpose of sandboxing is to limit the permissions and capabilities of library’s methods, in order to secure the usage of them. After obtaining the sandbox, the functions will be called through an Sandbox API (being called `api` in the current test) and so, the @@ -50,10 +70,12 @@ Without using this type of argument when running, the output format is set by default.* #### CMake observations resume: + * linking pffft and fftpack (which contains necessary functions for pffft) * set math library #### Sandboxed main observations resume: + * containing two testing parts (fft / pffft benchmarks) * showing the performance of the transformations implies testing them through various FFT dimenstions. diff --git a/oss-internship-2020/pffft/main_pffft_sandboxed.cc b/contrib/pffft/main_pffft_sandboxed.cc similarity index 99% rename from oss-internship-2020/pffft/main_pffft_sandboxed.cc rename to contrib/pffft/main_pffft_sandboxed.cc index e9a6abb..41b5523 100644 --- a/oss-internship-2020/pffft/main_pffft_sandboxed.cc +++ b/contrib/pffft/main_pffft_sandboxed.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include #include #include @@ -21,6 +21,7 @@ #include #include +#include "gflags/gflags.h" #include "pffft_sapi.sapi.h" // NOLINT(build/include) #include "sandboxed_api/util/flag.h" #include "sandboxed_api/vars.h" diff --git a/oss-internship-2020/pffft/.gitignore b/oss-internship-2020/pffft/.gitignore deleted file mode 100644 index eb6d948..0000000 --- a/oss-internship-2020/pffft/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.o -*.a -pffft_main From 59b942b2563c36ba9467e09514c0bd99143652d6 Mon Sep 17 00:00:00 2001 From: Sandboxed API Team Date: Wed, 9 Feb 2022 06:53:28 -0800 Subject: [PATCH 07/23] Add a little more logging to failure cases. PiperOrigin-RevId: 427459159 Change-Id: I34b6027cccfc4b3903ef4deeb9c133598b6667d4 --- sandboxed_api/sandbox2/comms.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sandboxed_api/sandbox2/comms.cc b/sandboxed_api/sandbox2/comms.cc index 482a6c8..9b27e0d 100644 --- a/sandboxed_api/sandbox2/comms.cc +++ b/sandboxed_api/sandbox2/comms.cc @@ -555,9 +555,11 @@ bool Comms::Recv(void* data, size_t len) { // Internal helper method (low level). bool Comms::RecvTL(uint32_t* tag, size_t* length) { if (!Recv(reinterpret_cast(tag), sizeof(*tag))) { + SAPI_RAW_VLOG(2, "RecvTL: Can't read tag"); return false; } if (!Recv(reinterpret_cast(length), sizeof(*length))) { + SAPI_RAW_VLOG(2, "RecvTL: Can't read length for tag %u", *tag); return false; } if (*length > GetMaxMsgSize()) { From e613bdfaebc05f4ce931c1da6a33934133f8b633 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sun, 30 Jan 2022 19:07:43 -0500 Subject: [PATCH 08/23] Sandbox TurboJPEG Took a few tries to get everything implemented. --- contrib/turbojpeg/CMakeLists.txt | 44 +++++ contrib/turbojpeg/tests/CMakeLists.txt | 26 +++ contrib/turbojpeg/tests/sample.jpeg | Bin 0 -> 257 bytes contrib/turbojpeg/tests/sample.rgb | Bin 0 -> 2412 bytes .../turbojpeg/tests/turbojpeg_sapi_test.cc | 180 ++++++++++++++++++ contrib/turbojpeg/turbojpeg_sapi.h | 44 +++++ 6 files changed, 294 insertions(+) create mode 100644 contrib/turbojpeg/CMakeLists.txt create mode 100644 contrib/turbojpeg/tests/CMakeLists.txt create mode 100644 contrib/turbojpeg/tests/sample.jpeg create mode 100644 contrib/turbojpeg/tests/sample.rgb create mode 100644 contrib/turbojpeg/tests/turbojpeg_sapi_test.cc create mode 100644 contrib/turbojpeg/turbojpeg_sapi.h diff --git a/contrib/turbojpeg/CMakeLists.txt b/contrib/turbojpeg/CMakeLists.txt new file mode 100644 index 0000000..30bb68f --- /dev/null +++ b/contrib/turbojpeg/CMakeLists.txt @@ -0,0 +1,44 @@ +# Copyright 2022 Google LLC +# +# 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 +# +# https://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. + +cmake_minimum_required(VERSION 3.13) +project(turbojpeg-sapi CXX C) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +if(NOT TARGET sapi::sapi) + set(SAPI_ROOT "../.." CACHE PATH "Path to the Sandboxed API source tree") + add_subdirectory("${SAPI_ROOT}" + "${CMAKE_BINARY_DIR}/sandboxed-api-build" + EXCLUDE_FROM_ALL) +endif() + +find_package(PkgConfig REQUIRED) +pkg_check_modules(TURBOJPEG REQUIRED IMPORTED_TARGET libturbojpeg) + +add_sapi_library(turbojpeg_sapi + INPUTS "${TURBOJPEG_INCLUDEDIR}/turbojpeg.h" + LIBRARY turbojpeg + LIBRARY_NAME TurboJPEG + NAMESPACE "turbojpeg_sapi" +) + +target_include_directories(turbojpeg_sapi INTERFACE + "${PROJECT_BINARY_DIR}" +) + +if(SAPI_ENABLE_TESTS) + add_subdirectory(tests) +endif() diff --git a/contrib/turbojpeg/tests/CMakeLists.txt b/contrib/turbojpeg/tests/CMakeLists.txt new file mode 100644 index 0000000..dae2462 --- /dev/null +++ b/contrib/turbojpeg/tests/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright 2022 Google LLC +# +# 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 +# +# https://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(GoogleTest) + +add_executable(turbojpeg_sapi_test turbojpeg_sapi_test.cc) + +target_link_libraries(turbojpeg_sapi_test PRIVATE + turbojpeg_sapi + sapi::base + gtest + gmock +) + +gtest_discover_tests(turbojpeg_sapi_test PROPERTIES ENVIRONMENT "TEST_FILES_DIR=${PROJECT_SOURCE_DIR}/files") diff --git a/contrib/turbojpeg/tests/sample.jpeg b/contrib/turbojpeg/tests/sample.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..c91c82d602db0d658db369e1339078a5c8d780c2 GIT binary patch literal 257 zcmex=>ukC3pCfH06P@c#e5MHm0yV&DL2vS-+HZt49(tLe2zUd4VFURi#}4})7Q k{%G;JrL8Zz`mLskFVeDk6%p65;`7SHDkVI^FaO^J08pDpnE(I) literal 0 HcmV?d00001 diff --git a/contrib/turbojpeg/tests/sample.rgb b/contrib/turbojpeg/tests/sample.rgb new file mode 100644 index 0000000000000000000000000000000000000000..4abc9f0b5bf25b034874b6f5b7df0fc02f8c2d92 GIT binary patch literal 2412 vcmezWf0%&h!v$OzE@0 + +#include +#include +#include +#include + +#include "../turbojpeg_sapi.h" // NOLINT(build/include) +#include "gflags/gflags.h" +#include "glog/logging.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "sandboxed_api/testing.h" +#include "sandboxed_api/util/fileops.h" +#include "sandboxed_api/util/path.h" +#include "sandboxed_api/util/status_matchers.h" +#include "turbojpeg_sapi.sapi.h" // NOLINT(build/include) + +namespace { + +using ::sapi::IsOk; +using ::testing::Eq; +using ::testing::Not; +using ::testing::NotNull; +using ::testing::StrEq; + +class TurboJpegSapiSandboxTest : public testing::Test { + protected: + static void SetUpTestSuite() { + ASSERT_THAT(getenv("TEST_FILES_DIR"), NotNull()); + sandbox_ = new TurboJpegSapiSandbox(); + ASSERT_THAT(sandbox_->Init(), IsOk()); + api_ = new turbojpeg_sapi::TurboJPEGApi(sandbox_); + } + static void TearDownTestSuite() { + delete api_; + delete sandbox_; + } + + static std::string GetTurboJpegErrorStr(sapi::v::Ptr* handle) { + auto errmsg_ptr = api_->tjGetErrorStr2(handle); + if (!errmsg_ptr.ok()) return "Error getting error message"; + auto errmsg = + sandbox_->GetCString(sapi::v::RemotePtr(errmsg_ptr.value()), 256); + if (!errmsg.ok()) return "Error getting error message"; + return errmsg.value(); + } + static turbojpeg_sapi::TurboJPEGApi* api_; + static TurboJpegSapiSandbox* sandbox_; +}; + +turbojpeg_sapi::TurboJPEGApi* TurboJpegSapiSandboxTest::api_; +TurboJpegSapiSandbox* TurboJpegSapiSandboxTest::sandbox_; + +std::string GetTestFilePath(const std::string& filename) { + return sapi::file::JoinPath(getenv("TEST_FILES_DIR"), filename); +} + +std::streamsize GetStreamSize(std::ifstream& stream) { + stream.seekg(0, std::ios_base::end); + std::streamsize ssize = stream.tellg(); + stream.seekg(0, std::ios_base::beg); + + return ssize; +} + +absl::StatusOr> ReadFile(const std::string& in_file, + size_t expected_size = SIZE_MAX) { + std::ifstream f(GetTestFilePath(in_file)); + if (!f.is_open()) { + return absl::UnavailableError("File could not be opened"); + } + std::streamsize ssize = GetStreamSize(f); + if (expected_size != SIZE_MAX && ssize != expected_size) { + return absl::UnavailableError("Incorrect size of file"); + } + std::vector inbuf((ssize)); + f.read(reinterpret_cast(inbuf.data()), ssize); + if (ssize != f.gcount()) { + return absl::UnavailableError("Premature end of file"); + } + if (f.fail() || f.eof()) { + return absl::UnavailableError("Error reading file"); + } + return inbuf; +} + +TEST_F(TurboJpegSapiSandboxTest, Compressor) { + absl::StatusOr compression_handle_raw = api_->tjInitCompress(); + ASSERT_THAT(compression_handle_raw, IsOk()); + ASSERT_THAT(compression_handle_raw.value(), NotNull()); + sapi::v::RemotePtr compression_handle{compression_handle_raw.value()}; + auto result = ReadFile("sample.rgb", 12 * 67 * 3); + ASSERT_THAT(result, IsOk()); + sapi::v::Array array(result->data(), result->size()); + + sapi::v::GenericPtr buffer; + unsigned long actual_length; + { + sapi::v::ULong length{0}; + auto result = api_->tjCompress2(&compression_handle, array.PtrBefore(), 12, + 36, 67, TJPF_RGB, buffer.PtrAfter(), + length.PtrBoth(), TJSAMP_444, 10, 0); + ASSERT_THAT(result, IsOk()); + ASSERT_THAT(result.value(), Eq(0)) + << "Error from sandboxee: " + << GetTurboJpegErrorStr(&compression_handle); + ASSERT_TRUE(buffer.GetValue()); + ASSERT_TRUE(buffer.GetRemote()); + ASSERT_TRUE((actual_length = length.GetValue())); + } + auto value = buffer.GetValue(); + + auto destroy_result = api_->tjDestroy(&compression_handle); + ASSERT_THAT(destroy_result, IsOk()); + ASSERT_THAT(destroy_result.value(), Eq(0)); +} + +TEST_F(TurboJpegSapiSandboxTest, Decompressor) { + absl::StatusOr decompression_handle_raw = api_->tjInitDecompress(); + ASSERT_THAT(decompression_handle_raw, IsOk()); + ASSERT_THAT(decompression_handle_raw.value(), NotNull()); + sapi::v::RemotePtr decompression_handle{decompression_handle_raw.value()}; + auto result = ReadFile("sample.jpeg"); + ASSERT_THAT(result, IsOk()); + sapi::v::Array array(result->data(), result->size()); + + sapi::v::Int width{0}; + sapi::v::Int height{0}; + sapi::v::Int subsamp{0}; + sapi::v::Int colorspace{0}; + auto decompress_result = api_->tjDecompressHeader3( + &decompression_handle, array.PtrBefore(), result->size(), + width.PtrAfter(), height.PtrAfter(), subsamp.PtrAfter(), + colorspace.PtrAfter()); + ASSERT_THAT(decompress_result, IsOk()); + ASSERT_THAT(decompress_result.value(), Eq(0)) + << "Error from sandboxee: " + << GetTurboJpegErrorStr(&decompression_handle); + + ASSERT_THAT(width.GetValue(), Eq(67)); + ASSERT_THAT(height.GetValue(), Eq(12)); + ASSERT_THAT(subsamp.GetValue(), Eq(TJSAMP_GRAY)); + ASSERT_THAT(colorspace.GetValue(), Eq(TJCS_GRAY)); + + auto arr = sapi::v::Array(12 * 67 * 3); + decompress_result = api_->tjDecompress2( + &decompression_handle, array.PtrBefore(), result->size(), arr.PtrAfter(), + 12, 36, 67, TJCS_RGB, 0); + ASSERT_THAT(decompress_result, IsOk()); + EXPECT_THAT(decompress_result.value(), Eq(0)) + << "Error from sandboxee: " + << GetTurboJpegErrorStr(&decompression_handle); + + decompress_result = api_->tjDestroy(&decompression_handle); + ASSERT_THAT(decompress_result, IsOk()); + ASSERT_THAT(decompress_result.value(), Eq(0)); +} +} // namespace + +int main(int argc, char* argv[]) { + ::google::InitGoogleLogging(program_invocation_short_name); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/contrib/turbojpeg/turbojpeg_sapi.h b/contrib/turbojpeg/turbojpeg_sapi.h new file mode 100644 index 0000000..2c62cd3 --- /dev/null +++ b/contrib/turbojpeg/turbojpeg_sapi.h @@ -0,0 +1,44 @@ +// Copyright 2022 Google LLC +// +// 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 +// +// https://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 CONTRIB_TURBOJPEG_TURBOJPEG_SAPI_H_ +#define CONTRIB_TURBOJPEG_TURBOJPEG_SAPI_H_ + +#include + +#include "turbojpeg_sapi.sapi.h" // NOLINT(build/include) +#include "sandboxed_api/util/fileops.h" +class TurboJpegSapiSandbox : public turbojpeg_sapi::TurboJPEGSandbox { + public: + std::unique_ptr ModifyPolicy( + sandbox2::PolicyBuilder*) override { + return sandbox2::PolicyBuilder() + .AllowSystemMalloc() + .AllowRead() + .AllowStat() + .AllowWrite() + .AllowExit() + .AllowSyscalls({ + __NR_futex, + __NR_close, + __NR_lseek, + __NR_getpid, + __NR_clock_gettime, + }) + .AllowLlvmSanitizers() + .BuildOrDie(); + } +}; + +#endif // CONTRIB_TURBOJPEG_TURBOJPEG_SAPI_H_ From 7004d59150c9fbfaa3e5fd1872affffd1ab14fe8 Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Thu, 10 Feb 2022 01:40:38 -0800 Subject: [PATCH 09/23] Remove pffft submodule entry PiperOrigin-RevId: 427686350 Change-Id: If75bb7e094f8a1c14b3e0c8f26d0f21b1e45a6dc --- .gitmodules | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 9575a07..5690ba9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,6 @@ [submodule "oss-internship-2020/openjpeg/openjpeg"] path = oss-internship-2020/openjpeg/openjpeg url = https://github.com/uclouvain/openjpeg.git -[submodule "oss-internship-2020/pffft/master"] - path = oss-internship-2020/pffft/master - url = https://bitbucket.org/jpommier/pffft/src/master/ [submodule "oss-internship-2020/gdal/gdal"] path = oss-internship-2020/gdal/gdal url = https://github.com/OSGeo/gdal/ From 36d0f928c6e78747ffb122372d4d94d97fd0a193 Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Fri, 11 Feb 2022 07:19:01 -0800 Subject: [PATCH 10/23] Apply page offset during stack unwinding/symbolization This fixes a couple of tests in the open source version of the code. Internally, since we are using a different ELF loader, the page offset will always be zero. Hence we never notices this was broken. PiperOrigin-RevId: 427996428 Change-Id: I44c5b5610b074cf69b9f0c5eeb051be50923e351 --- sandboxed_api/sandbox2/unwind/unwind.cc | 6 ++++-- sandboxed_api/sandbox2/util/minielf_test.cc | 17 +++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/sandboxed_api/sandbox2/unwind/unwind.cc b/sandboxed_api/sandbox2/unwind/unwind.cc index ddeaba0..e62a458 100644 --- a/sandboxed_api/sandbox2/unwind/unwind.cc +++ b/sandboxed_api/sandbox2/unwind/unwind.cc @@ -177,8 +177,10 @@ absl::StatusOr LoadSymbolsMap(pid_t pid) { for (const ElfFile::Symbol& symbol : elf->symbols()) { if (elf->position_independent()) { - if (symbol.address < entry.end - entry.start) { - addr_to_symbol[symbol.address + entry.start] = symbol.name; + if (symbol.address >= entry.pgoff && + symbol.address - entry.pgoff < entry.end - entry.start) { + addr_to_symbol[symbol.address + entry.start - entry.pgoff] = + symbol.name; } } else { if (symbol.address >= entry.start && symbol.address < entry.end) { diff --git a/sandboxed_api/sandbox2/util/minielf_test.cc b/sandboxed_api/sandbox2/util/minielf_test.cc index 1e973c3..cccd97f 100644 --- a/sandboxed_api/sandbox2/util/minielf_test.cc +++ b/sandboxed_api/sandbox2/util/minielf_test.cc @@ -26,13 +26,14 @@ #include "sandboxed_api/util/file_helpers.h" #include "sandboxed_api/util/status_matchers.h" -extern "C" void ExportedFunctionName() { +extern "C" void ExportedFunction() { // Don't do anything - used to generate a symbol. } namespace file = ::sapi::file; using ::sapi::GetTestSourcePath; using ::sapi::IsOk; +using ::testing::ElementsAre; using ::testing::Eq; using ::testing::IsTrue; using ::testing::Ne; @@ -65,19 +66,20 @@ TEST(MinielfTest, SymbolResolutionWorks) { ParseProcMaps(maps_buffer)); // Find maps entry that covers this entry. - uint64_t function_address = reinterpret_cast(ExportedFunctionName); - auto function_entry = + uint64_t function_address = reinterpret_cast(&ExportedFunction); + auto entry = absl::c_find_if(maps, [function_address](const MapsEntry& entry) { return entry.start <= function_address && entry.end > function_address; }); - ASSERT_THAT(function_entry, Ne(maps.end())); - function_address -= function_entry->start; + ASSERT_THAT(entry, Ne(maps.end())); auto function_symbol = absl::c_find_if(elf.symbols(), [](const ElfFile::Symbol& symbol) { - return symbol.name == "ExportedFunctionName"; + return symbol.name == "ExportedFunction"; }); ASSERT_THAT(function_symbol, Ne(elf.symbols().end())); + + function_address -= entry->start - entry->pgoff; EXPECT_THAT(function_symbol->address, Eq(function_address)); } @@ -86,8 +88,7 @@ TEST(MinielfTest, ImportedLibraries) { ElfFile elf, ElfFile::ParseFromFile( GetTestSourcePath("sandbox2/util/testdata/hello_world"), ElfFile::kLoadImportedLibraries)); - std::vector imported_libraries = {"libc.so.6"}; - EXPECT_THAT(elf.imported_libraries(), Eq(imported_libraries)); + EXPECT_THAT(elf.imported_libraries(), ElementsAre("libc.so.6")); } } // namespace From d1ed8ac66e2b7c19a4e28a29be5cc36a544eaf3b Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Fri, 11 Feb 2022 09:08:23 -0800 Subject: [PATCH 11/23] Avoid compiler crash with Clang 6.0 Instead of C++17 structured bindings, use a plain `const auto&` and annotate arguments with comments instead. We still support Clang 6.0, as that is the compiler that ships with Ubuntu 18.04 LTS by default. PiperOrigin-RevId: 428016214 Change-Id: I3a43b2d47c6825ac4425d22018750282cfe23c1b --- sandboxed_api/util/status.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sandboxed_api/util/status.cc b/sandboxed_api/util/status.cc index 323915d..df93823 100644 --- a/sandboxed_api/util/status.cc +++ b/sandboxed_api/util/status.cc @@ -31,8 +31,11 @@ void SaveStatusToProto(const absl::Status& status, StatusProto* out) { absl::Status MakeStatusFromProto(const StatusProto& proto) { absl::Status status(static_cast(proto.code()), proto.message()); - for (const auto& [type_key, payload] : proto.payloads()) { - status.SetPayload(type_key, absl::Cord(payload)); + // Note: Using C++17 structured bindings instead of `entry` crashes Clang 6.0 + // on Ubuntu 18.04 (bionic). + for (const auto& entry : proto.payloads()) { + status.SetPayload(/*type_url=*/entry.first, + /*payload=*/absl::Cord(entry.second)); } return status; } From 544d438e71bfbf5189d10f0ea414c1ba6e2954ba Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sat, 12 Feb 2022 21:43:49 -0500 Subject: [PATCH 12/23] Fix a syntax error in the zstd example It breaks the SAPI_ENABLE_CONTRIB_TESTS build. --- contrib/zstd/example/main.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/zstd/example/main.cc b/contrib/zstd/example/main.cc index cb9faf1..3c899bc 100644 --- a/contrib/zstd/example/main.cc +++ b/contrib/zstd/example/main.cc @@ -64,7 +64,7 @@ absl::Status FileDescriptor(ZstdApi& api, std::string infile_s, return absl::UnavailableError(absl::StrCat("Unable to open ", outfile_s)); } - if (absl::GetFlag(FLAGS_memory_mode) { + if (absl::GetFlag(FLAGS_memory_mode)) { if (absl::GetFlag(FLAGS_decompress)) { return DecompressInMemoryFD(api, infd, outfd); } From 789c436a3e7497dac24b1c9459e469f5bcdaa7c7 Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Mon, 14 Feb 2022 05:56:49 -0800 Subject: [PATCH 13/23] CI: Run tests in VM based builders This adds a first basic test to be run using GitHub Actions on push and pull request for the CMake build (internally we run everything on Bazel/Blaze). The Ubuntu runners are implemented as full VMs, so we can run tests directly. In order to run Sandboxed API/Sandbox2 tests inside a container, it must be started as privileged, unconfined and retain its capabilities. Since GitHub does not support modifying the Docker invocation for container based workflows, we need to manually run the `docker` command. Until #118 is fixed, this change makes GitHub ignore the test failure on Fedora. PiperOrigin-RevId: 428485354 Change-Id: I6b55c5441c4c27b018d19498d2296c7d3da65846 --- .github/workflows/fedora-cmake.yml | 53 ++++++++++++++++++++---------- .github/workflows/ubuntu-cmake.yml | 18 +++++----- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/.github/workflows/fedora-cmake.yml b/.github/workflows/fedora-cmake.yml index 7b49090..73a0c42 100644 --- a/.github/workflows/fedora-cmake.yml +++ b/.github/workflows/fedora-cmake.yml @@ -13,36 +13,55 @@ jobs: include: - container: fedora:35 compiler: gcc # GCC 11 - ignore-errors: false - # TODO(cblichmann): Add clang-13 build to matrix (currently fails) + ignore-errors: true # Stack trace test fails on Fedora (issue #118) runs-on: ubuntu-latest continue-on-error: ${{ matrix.ignore-errors }} - container: - image: ${{ matrix.container }} + env: + RUN_CMD: docker exec --tty ${{matrix.compiler}}-build-container steps: - uses: actions/checkout@v2 + - name: Prepare container + # Note: For the sandbox tests to work, we need a privileged, unconfined + # container that retains its capabilities. + run: | + docker run --name ${{matrix.compiler}}-build-container \ + --tty \ + --privileged \ + --cap-add ALL \ + --security-opt apparmor:unconfined \ + -v $GITHUB_WORKSPACE:$GITHUB_WORKSPACE \ + -e TERM=dumb \ + -e BUILD_TYPE \ + -e GITHUB_WORKSPACE \ + -d ${{matrix.container}} \ + sleep infinity + - name: Install build tools run: | - dnf update -y - dnf install -y git make automake patch glibc-static libstdc++-static \ - cmake ninja-build python3 python3-pip clang-devel libcap-devel - - - name: Install/configure Clang compiler toolchain - if: matrix.compiler == 'clang' - run: | - echo "CXX=clang++" >> $GITHUB_ENV - echo "CC=clang" >> $GITHUB_ENV + $RUN_CMD dnf update -y --quiet + $RUN_CMD dnf install -y --quiet git make automake patch glibc-static \ + libstdc++-static cmake ninja-build python3 python3-pip clang-devel \ + libcap-devel - name: Create Build Environment run: | - pip3 install absl-py clang - cmake -E make_directory $GITHUB_WORKSPACE/build + $RUN_CMD pip3 install --progress-bar=off absl-py clang + $RUN_CMD cmake -E make_directory $GITHUB_WORKSPACE/build - name: Configure CMake - run: cmake $GITHUB_WORKSPACE -G Ninja -DCMAKE_BUILD_TYPE=$BUILD_TYPE + run: | + $RUN_CMD cmake -S $GITHUB_WORKSPACE -B $GITHUB_WORKSPACE/build \ + -G Ninja -DCMAKE_BUILD_TYPE=$BUILD_TYPE - name: Build - run: cmake --build $GITHUB_WORKSPACE --config $BUILD_TYPE + run: | + $RUN_CMD cmake --build $GITHUB_WORKSPACE/build --config $BUILD_TYPE + + - name: Test + run: | + $RUN_CMD ctest --test-dir $GITHUB_WORKSPACE/build -C $BUILD_TYPE \ + --output-on-failure \ + -R SapiTest diff --git a/.github/workflows/ubuntu-cmake.yml b/.github/workflows/ubuntu-cmake.yml index 29963c5..4b890dd 100644 --- a/.github/workflows/ubuntu-cmake.yml +++ b/.github/workflows/ubuntu-cmake.yml @@ -62,17 +62,17 @@ jobs: - name: Create Build Environment run: | pip3 install absl-py clang - cmake -E make_directory ${{runner.workspace}}/build + cmake -E make_directory $GITHUB_WORKSPACE/build - name: Configure CMake - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -G Ninja -DCMAKE_BUILD_TYPE=$BUILD_TYPE + run: | + cmake $GITHUB_WORKSPACE -G Ninja -DCMAKE_BUILD_TYPE=$BUILD_TYPE - name: Build - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE + run: | + cmake --build $GITHUB_WORKSPACE --config $BUILD_TYPE - # TODO(cblichmann): Before enabling this, make sure all OSS tests pass - #- name: Test - # working-directory: ${{runner.workspace}}/build - # run: ctest -C $BUILD_TYPE + - name: Test + run: | + ctest $GITHUB_WORKSPACE -C $BUILD_TYPE --output-on-failure \ + -R SapiTest From d39d63d6ec9fd4de79790e65ff6d38de7338bd65 Mon Sep 17 00:00:00 2001 From: Mariusz Zaborski Date: Mon, 31 Jan 2022 05:02:14 -0500 Subject: [PATCH 14/23] Sandbox c-blosc --- contrib/README.md | 11 +- contrib/c-blosc/CMakeLists.txt | 90 +++++ contrib/c-blosc/example/CMakeLists.txt | 32 ++ contrib/c-blosc/example/main.cc | 94 +++++ contrib/c-blosc/files/text | 59 ++++ contrib/c-blosc/files/text.blosclz | Bin 0 -> 8590 bytes contrib/c-blosc/files/text.lz4 | Bin 0 -> 9827 bytes contrib/c-blosc/files/text.lz4hc | Bin 0 -> 7939 bytes contrib/c-blosc/files/text.zlib | Bin 0 -> 5861 bytes contrib/c-blosc/files/text.zstd | Bin 0 -> 5701 bytes .../c-blosc/patches/c-blosc.blosc.cmake.patch | 19 + contrib/c-blosc/patches/c-blosc.cmake.patch | 25 ++ contrib/c-blosc/sandboxed.h | 42 +++ contrib/c-blosc/test/CMakeLists.txt | 33 ++ contrib/c-blosc/test/test_blosc.cc | 325 ++++++++++++++++++ contrib/c-blosc/utils/utils_blosc.cc | 121 +++++++ contrib/c-blosc/utils/utils_blosc.h | 29 ++ 17 files changed, 875 insertions(+), 5 deletions(-) create mode 100644 contrib/c-blosc/CMakeLists.txt create mode 100644 contrib/c-blosc/example/CMakeLists.txt create mode 100644 contrib/c-blosc/example/main.cc create mode 100644 contrib/c-blosc/files/text create mode 100644 contrib/c-blosc/files/text.blosclz create mode 100644 contrib/c-blosc/files/text.lz4 create mode 100644 contrib/c-blosc/files/text.lz4hc create mode 100644 contrib/c-blosc/files/text.zlib create mode 100644 contrib/c-blosc/files/text.zstd create mode 100644 contrib/c-blosc/patches/c-blosc.blosc.cmake.patch create mode 100644 contrib/c-blosc/patches/c-blosc.cmake.patch create mode 100644 contrib/c-blosc/sandboxed.h create mode 100644 contrib/c-blosc/test/CMakeLists.txt create mode 100644 contrib/c-blosc/test/test_blosc.cc create mode 100644 contrib/c-blosc/utils/utils_blosc.cc create mode 100644 contrib/c-blosc/utils/utils_blosc.h diff --git a/contrib/README.md b/contrib/README.md index 04614c7..036b2ba 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -5,11 +5,12 @@ libraries. ## Projects Sandboxed -Directory | Project | Home Page | Integration ------------ | ------------------------------------------------- | -------------------------------------------------------------------- | ----------- -`jsonnet/` | Jsonnet - The Data Templating Language | [github.com/google/jsonnet](https://github.com/google/jsonnet) | CMake -`hunspell/` | Hunspell - The most popular spellchecking library | [github.com/hunspell/hunspell](https://github.com/hunspell/hunspell) | CMake -`zstd/` | Zstandard - Fast real-time compression algorithm | [github.com/facebook/zstd](https://github.com/facebook/zstd) | CMake +Directory | Project | Home Page | Integration +----------- | --------------------------------------------------------------- | -------------------------------------------------------------------- | ----------- +`c-blosc/` | c-blosc A blocking, shuffling and loss-less compression library | [github.com/Blosc/c-blosc](https://github.com/Blosc/c-blosc) | CMake +`jsonnet/` | Jsonnet - The Data Templating Language | [github.com/google/jsonnet](https://github.com/google/jsonnet) | CMake +`hunspell/` | Hunspell - The most popular spellchecking library | [github.com/hunspell/hunspell](https://github.com/hunspell/hunspell) | CMake +`zstd/` | Zstandard - Fast real-time compression algorithm | [github.com/facebook/zstd](https://github.com/facebook/zstd) | CMake ## Projects Shipping with Sandboxed API Sandboxes diff --git a/contrib/c-blosc/CMakeLists.txt b/contrib/c-blosc/CMakeLists.txt new file mode 100644 index 0000000..8e564a1 --- /dev/null +++ b/contrib/c-blosc/CMakeLists.txt @@ -0,0 +1,90 @@ +# Copyright 2022 Google LLC +# +# 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 +# +# https://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. + +cmake_minimum_required(VERSION 3.13..3.22) + +project(sapi_blosc CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +if(NOT TARGET sapi::sapi) + set(SAPI_ROOT "../.." CACHE PATH "Path to the Sandboxed API source tree") + add_subdirectory( + "${SAPI_ROOT}" + "${CMAKE_BINARY_DIR}/sandboxed-api-build" + EXCLUDE_FROM_ALL + ) +endif() + +set(HIDE_SYMBOLS off) +FetchContent_Declare( + libblosc + + GIT_REPOSITORY https://github.com/Blosc/c-blosc.git + GIT_TAG a0e5c18d37db8e6f1003254a574c8062c5b45e00 + PATCH_COMMAND patch < "${CMAKE_SOURCE_DIR}/patches/c-blosc.cmake.patch" && cd blosc && patch < "${CMAKE_SOURCE_DIR}/patches/c-blosc.blosc.cmake.patch" +) +FetchContent_MakeAvailable(libblosc) + +add_sapi_library( + sapi_blosc + + FUNCTIONS + blosc_init + blosc_destroy + + blosc_compress + blosc_decompress + + blosc_get_nthreads + blosc_set_nthreads + + blosc_get_compressor + blosc_set_compressor + + blosc_list_compressors + + blosc_get_version_string + + blosc_get_blocksize + blosc_set_blocksize + + blosc_set_splitmode + + blosc_cbuffer_sizes + blosc_cbuffer_validate + blosc_cbuffer_versions + + INPUTS + "${libblosc_SOURCE_DIR}/blosc/blosc.h" + + LIBRARY blosc_static + LIBRARY_NAME Cblosc + NAMESPACE "" +) + +add_library(sapi_contrib::blosc ALIAS sapi_blosc) + +target_include_directories(sapi_blosc INTERFACE + "${PROJECT_BINARY_DIR}" +) + +if (SAPI_ENABLE_EXAMPLES) + add_subdirectory(example) +endif() + +if (SAPI_ENABLE_TESTS) + add_subdirectory(test) +endif() diff --git a/contrib/c-blosc/example/CMakeLists.txt b/contrib/c-blosc/example/CMakeLists.txt new file mode 100644 index 0000000..b5631c2 --- /dev/null +++ b/contrib/c-blosc/example/CMakeLists.txt @@ -0,0 +1,32 @@ +# Copyright 2022 Google LLC +# +# 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 +# +# https://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. + +add_executable( + sapi_miniblosc + + main.cc + ../utils/utils_blosc.cc +) + +target_include_directories(sapi_miniblosc INTERFACE + "${SAPI_SOURCE_DIR}" +) + +target_link_libraries( + sapi_miniblosc PRIVATE + + sapi_blosc + sapi::sapi + absl::flags_parse +) diff --git a/contrib/c-blosc/example/main.cc b/contrib/c-blosc/example/main.cc new file mode 100644 index 0000000..bd89c3a --- /dev/null +++ b/contrib/c-blosc/example/main.cc @@ -0,0 +1,94 @@ +// Copyright 2022 Google LLC +// +// 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 +// +// https://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 +#include +#include + +#include +#include +#include + +#include "contrib/c-blosc/sandboxed.h" +#include "contrib/c-blosc/utils/utils_blosc.h" + +ABSL_FLAG(bool, decompress, false, "decompress"); +ABSL_FLAG(int, clevel, 5, "compression level"); +ABSL_FLAG(uint32_t, nthreads, 5, "number of threads"); +ABSL_FLAG(std::string, compressor, "blosclz", + "compressor engine. Available: blosclz, lz4, lz4hc, zlib, zstd"); + +absl::Status Stream(CbloscApi& api, std::string& infile_s, + std::string& outfile_s) { + std::ifstream infile(infile_s, std::ios::binary); + if (!infile.is_open()) { + return absl::UnavailableError(absl::StrCat("Unable to open ", infile_s)); + } + std::ofstream outfile(outfile_s, std::ios::binary); + if (!outfile.is_open()) { + return absl::UnavailableError(absl::StrCat("Unable to open ", outfile_s)); + } + + std::string compressor(absl::GetFlag(FLAGS_compressor)); + + if (absl::GetFlag(FLAGS_decompress)) { + return Decompress(api, infile, outfile, 5); + } + + return Compress(api, infile, outfile, absl::GetFlag(FLAGS_clevel), compressor, + absl::GetFlag(FLAGS_nthreads)); +} + +int main(int argc, char* argv[]) { + std::string prog_name(argv[0]); + google::InitGoogleLogging(argv[0]); + std::vector args = absl::ParseCommandLine(argc, argv); + + if (args.size() != 3) { + std::cerr << "Usage:\n " << prog_name << " INPUT OUTPUT\n"; + return EXIT_FAILURE; + } + + CbloscSapiSandbox sandbox; + if (!sandbox.Init().ok()) { + std::cerr << "Unable to start sandbox\n"; + return EXIT_FAILURE; + } + CbloscApi api(&sandbox); + + if (absl::Status status = api.blosc_init(); !status.ok()) { + std::cerr << "Unable to init library\n"; + std::cerr << status << std::endl; + return EXIT_FAILURE; + } + + std::string infile_s(args[1]); + std::string outfile_s(args[2]); + + if (absl::Status status = Stream(api, infile_s, outfile_s); !status.ok()) { + std::cerr << "Unable to "; + std::cerr << (absl::GetFlag(FLAGS_decompress) ? "de" : ""); + std::cerr << "compress file\n"; + std::cerr << status << std::endl; + return EXIT_FAILURE; + } + + if (absl::Status status = api.blosc_destroy(); !status.ok()) { + std::cerr << "Unable to uninitialize library\n"; + std::cerr << status << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/contrib/c-blosc/files/text b/contrib/c-blosc/files/text new file mode 100644 index 0000000..b792172 --- /dev/null +++ b/contrib/c-blosc/files/text @@ -0,0 +1,59 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam id ultricies neque, id blandit nisl. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas pharetra urna purus, a finibus erat sodales ac. Donec sed convallis felis, ac rutrum velit. Suspendisse dapibus ultrices euismod. Donec non erat scelerisque, pretium dui id, dignissim dolor. Vivamus id orci maximus, maximus sapien sed, tincidunt nunc. + +Praesent fringilla lectus lobortis dui posuere, ac mollis neque ultrices. Fusce nec quam eget neque feugiat mollis quis sit amet ipsum. Suspendisse hendrerit elit tincidunt neque tincidunt mattis. Nulla vitae neque sit amet metus consequat ultrices a ac augue. Nunc viverra lacinia ultrices. Vivamus in cursus ex, a sollicitudin purus. Curabitur et consectetur enim. Duis euismod magna eget velit tristique, at varius lorem posuere. Quisque non erat at diam blandit sollicitudin sit amet ac lectus. Maecenas nec consectetur augue, vel dignissim orci. Etiam rutrum ac massa at accumsan. + +Nullam congue lacus et eros ornare, et malesuada nunc varius. Mauris facilisis suscipit nisl non tristique. Proin lobortis diam vel mi tempus, sit amet iaculis eros iaculis. Phasellus a metus ac purus pellentesque fermentum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Suspendisse venenatis quam vel sapien scelerisque commodo. Donec nec urna feugiat, semper ipsum non, dapibus neque. Aenean egestas tortor sit amet interdum convallis. + +Cras sed enim scelerisque orci hendrerit porta. Duis egestas erat sollicitudin vestibulum convallis. Proin eget eros in neque ultricies posuere. Quisque sodales nisi nulla, non sollicitudin ipsum sodales in. Phasellus feugiat sollicitudin enim, in faucibus massa cursus nec. Donec arcu erat, ultricies eget nisi scelerisque, pharetra fringilla nisl. Maecenas et consectetur neque. Duis vel rhoncus lacus. Nulla nec lorem iaculis, consequat sapien a, semper leo. + +Praesent vehicula sem in tempor pulvinar. Sed porttitor volutpat nulla eget tempor. Phasellus sit amet quam justo. Donec sit amet lacus dolor. Ut finibus sollicitudin dui vitae tincidunt. Mauris commodo nec orci convallis accumsan. In dapibus urna ex, in convallis purus ornare vel. + +Suspendisse potenti. Nulla mi lorem, dictum accumsan mollis vitae, commodo vitae purus. Nullam accumsan lectus elit, vel volutpat lectus varius eget. Sed in condimentum neque. Mauris nibh arcu, dignissim sed ipsum a, imperdiet mattis neque. Nam eu pretium orci, a semper mauris. Proin dui lacus, auctor nec nibh sed, eleifend interdum libero. Vestibulum venenatis gravida risus in pulvinar. Sed sit amet leo vehicula, lacinia nisi et, dignissim ex. Fusce consectetur sollicitudin nisi. Pellentesque nec mauris vitae arcu iaculis semper eu eu odio. Ut facilisis erat a hendrerit egestas. Nullam tristique augue nunc, et ornare risus efficitur et. Quisque placerat risus metus, eu rutrum dui egestas et. Donec elementum sapien leo, vitae porttitor metus pharetra luctus. + +Ut non neque vel nibh accumsan luctus at eu ipsum. Ut vitae lacus vestibulum, sodales purus quis, elementum neque. Pellentesque a justo non massa finibus iaculis in sit amet mi. Nunc vel justo libero. Proin mollis ex quis nulla sollicitudin, quis porttitor justo imperdiet. Aenean vulputate semper consectetur. Aenean nibh tortor, viverra commodo magna sed, condimentum efficitur tellus. Maecenas eros mauris, tempor at porta ut, laoreet quis est. Proin eu purus a dolor aliquet bibendum nec ac velit. Phasellus quis tortor risus. Aliquam non felis quis massa scelerisque ullamcorper. Nulla tristique nunc ligula, quis semper lacus tincidunt ut. Maecenas in aliquet magna. Phasellus sed dolor id felis blandit efficitur a at nulla. + +Curabitur condimentum est non felis luctus, in tempus tellus tempus. Curabitur at quam nec mi vulputate sollicitudin quis a arcu. Phasellus lectus lorem, feugiat sit amet accumsan in, pretium ut leo. Integer vulputate ante ac nunc elementum varius. Nunc interdum tellus auctor, rutrum urna ut, imperdiet quam. Maecenas non accumsan lorem. Donec sem augue, tincidunt vel aliquam in, viverra et tellus. Duis lobortis, arcu quis scelerisque rhoncus, velit enim lacinia sem, sed commodo lorem ex cursus ligula. Integer nec auctor erat. Proin feugiat vel odio a fringilla. Nam dignissim augue elit. Nam sed lectus consequat, lacinia est vel, maximus eros. Aenean egestas ultricies odio. Mauris ut cursus ipsum. Fusce porttitor, eros sed sollicitudin porttitor, quam ipsum blandit nunc, ut sagittis metus diam tristique tortor. In sagittis odio odio, et ullamcorper augue gravida vel. + +Nam vulputate nulla ut faucibus pulvinar. Praesent in purus non orci semper imperdiet. Vivamus vel diam ornare, eleifend tellus sit amet, viverra orci. Proin porttitor ipsum et odio laoreet, quis efficitur nisl dictum. Vestibulum mollis, arcu ut semper iaculis, nulla sapien vehicula enim, quis vulputate dolor leo hendrerit felis. Vestibulum mi purus, tristique vel vulputate et, efficitur ac turpis. Aenean facilisis sed nisl ac iaculis. Proin scelerisque justo a diam commodo, ac volutpat nisi sodales. Aliquam hendrerit blandit sapien, eu pretium risus dictum nec. Aliquam ultrices tincidunt magna nec bibendum. Vivamus dapibus et lectus at suscipit. Phasellus maximus, nibh sed tempor convallis, arcu erat condimentum ex, id laoreet ex leo nec mi. Aenean ultricies eget quam non ultrices. Donec sollicitudin ex non elit mattis rutrum. + +Aliquam feugiat, elit a lobortis aliquam, orci augue rhoncus urna, et condimentum nibh est at est. Morbi eu diam quis nisi tristique tempus id ac tortor. Praesent a aliquet sem. Donec a mollis tellus, in dapibus ligula. Nullam quis dictum leo. Nam turpis diam, imperdiet id felis non, rutrum vehicula dui. Donec eleifend augue eu mauris tristique, non condimentum sapien dapibus. Fusce congue scelerisque suscipit. In in vulputate enim, malesuada mollis augue. Vestibulum tristique tempus ipsum vel venenatis. Quisque mauris urna, congue in lorem sed, dapibus convallis lorem. In eget arcu varius, tempor nisi quis, fermentum ipsum. Cras faucibus lacus sed massa pulvinar, vel viverra felis rhoncus. + +Nulla in nisi fringilla, molestie nisi at, dapibus arcu. Aenean ut justo eget neque tincidunt elementum. Ut varius, neque eu tempor pellentesque, magna turpis fermentum neque, eget pulvinar diam purus sit amet augue. Curabitur suscipit pharetra nisi egestas convallis. Aliquam et ex pulvinar eros malesuada viverra sed in libero. Quisque molestie dictum arcu at ultricies. Nulla id metus non lacus posuere vestibulum vel ut sem. Proin pulvinar nisl nisl, a imperdiet enim ultrices vel. Etiam malesuada posuere dignissim. Donec mollis nulla ut enim sollicitudin, nec commodo ante malesuada. Quisque sit amet augue eu quam malesuada mollis sed at ipsum. + +Aliquam quam est, maximus sed porta ut, placerat eu nunc. Vivamus ultrices ultricies tristique. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In et mi eu lacus rhoncus condimentum ultrices non velit. Suspendisse imperdiet, ipsum eu commodo euismod, est dolor sollicitudin augue, eu blandit risus tortor sit amet eros. Aliquam rutrum feugiat magna, sit amet aliquam tortor rutrum non. Fusce tempor rhoncus lectus vitae molestie. Sed ut tellus sagittis, congue enim mollis, malesuada ligula. Sed id tellus vel dolor commodo posuere malesuada et felis. + +Morbi quis risus nec lectus maximus tristique vehicula a lectus. Suspendisse ipsum urna, accumsan sit amet dui id, malesuada pellentesque diam. Sed sit amet tellus rutrum, placerat urna nec, tristique libero. Etiam luctus sit amet dui in rutrum. Etiam iaculis, tortor ut vulputate blandit, enim ligula euismod tortor, a porttitor leo ex sit amet lorem. Proin fermentum, est et fermentum laoreet, ante risus aliquam odio, nec consectetur dui leo in est. Etiam dapibus risus sit amet quam viverra gravida. Aenean at libero quis dolor consectetur ultricies. Maecenas rutrum mattis dui, ut auctor elit fringilla vitae. Nulla consequat tincidunt ligula ac dapibus. Vestibulum nisl erat, pharetra ac cursus sed, molestie sit amet justo. Vestibulum lobortis id ligula in elementum. Mauris non purus a magna feugiat tempus et at erat. In tincidunt diam vitae rutrum molestie. Pellentesque ut nisi laoreet, semper mi aliquet, rhoncus dolor. + +Pellentesque lacinia quis felis ac fringilla. Cras ante enim, efficitur non erat in, gravida mollis diam. Aliquam accumsan nulla nec eros aliquam fringilla. Mauris scelerisque aliquam fringilla. Aliquam lacinia mi orci, sit amet dignissim lorem lacinia at. Duis elit arcu, iaculis id risus at, porta sollicitudin sapien. Duis ante eros, consequat vitae massa at, auctor interdum nisi. Vivamus sit amet dui neque. Mauris tincidunt vestibulum arcu, consequat condimentum urna tincidunt vel. Sed a urna magna. Nullam in pharetra nulla. + +Nunc id purus at ipsum blandit consectetur sagittis non lorem. In sit amet aliquam ante. Integer convallis lectus eu lorem finibus vulputate. Morbi feugiat turpis id neque posuere, non elementum turpis volutpat. Nullam gravida, turpis vitae varius placerat, neque nisl bibendum velit, at suscipit eros ipsum et lorem. Integer arcu eros, gravida eu mauris condimentum, tincidunt euismod mi. Sed id elit vitae nulla aliquet laoreet. Sed pulvinar sollicitudin fermentum. Fusce semper ullamcorper mollis. Aenean laoreet, sem non cursus volutpat, metus elit scelerisque nisi, eget maximus neque enim non orci. + +Curabitur lacinia suscipit rutrum. Curabitur convallis eu lorem nec accumsan. Morbi et turpis sit amet erat mollis vehicula et eu risus. Nulla vel urna mollis, laoreet risus vel, auctor risus. Cras rutrum tempor ligula, sed fringilla justo tempor gravida. Nam tincidunt elementum dolor in sollicitudin. Donec pretium diam sed libero varius efficitur. Vivamus non nunc vulputate, accumsan ipsum sit amet, facilisis urna. Suspendisse suscipit, diam et ultrices maximus, enim sapien facilisis ligula, ut lacinia metus nisl eget felis. Proin egestas sollicitudin est ac auctor. Cras sapien turpis, interdum vitae laoreet sed, aliquam sed ex. Sed ullamcorper mi eu nisi varius facilisis. Nam fringilla at augue sit amet finibus. Mauris vitae facilisis purus. + +Suspendisse hendrerit lacus eget nisi bibendum sodales. In scelerisque sem eget est suscipit, in sollicitudin urna ultricies. Aenean ac mi non mauris congue viverra. Nam fermentum, purus ut consectetur volutpat, augue arcu luctus eros, quis efficitur felis mi nec ligula. Nullam eget feugiat ante. Donec rutrum, ex sit amet sodales venenatis, leo nibh euismod lacus, nec auctor purus urna in enim. Aliquam erat volutpat. Ut a ante rhoncus, cursus massa quis, porttitor mi. Nullam non lectus et mauris mollis lacinia. Curabitur euismod euismod nisi vitae malesuada. Donec vel finibus justo. Suspendisse lobortis efficitur ligula sed bibendum. + +Integer mauris nulla, auctor vel imperdiet vitae, commodo vel arcu. Nulla sit amet tempor ante, ac elementum dolor. Nam fermentum euismod est nec pulvinar. Etiam eleifend blandit nisl non mollis. Vestibulum porta sed nunc at aliquet. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec id sem eu eros lobortis malesuada. Maecenas cursus neque a purus rhoncus, fermentum sagittis enim viverra. Proin blandit laoreet est eget feugiat. + +Nulla nec nulla pellentesque, ullamcorper felis eget, bibendum augue. Sed lectus felis, tempor ac est non, commodo viverra augue. Ut bibendum arcu eget dapibus consectetur. Morbi id lectus vulputate, maximus leo sed, tincidunt leo. Nunc id nibh a lacus sagittis tristique at ac dolor. Nunc sed iaculis dolor. Cras maximus vulputate metus, a finibus magna sagittis non. Morbi porta fringilla enim, non pellentesque elit pellentesque eu. Vivamus pharetra dictum fermentum. Etiam consequat iaculis consequat. Morbi pharetra ante quam, quis efficitur purus finibus quis. Nulla at volutpat arcu. Curabitur augue lorem, lacinia eget augue vel, dignissim fringilla augue. Duis facilisis nulla turpis, ut fringilla quam efficitur ut. Cras tincidunt, tellus et dignissim ultricies, ligula ex pulvinar enim, vitae convallis erat ex ac neque. + +Mauris sed auctor lorem. Vestibulum non ligula viverra, rhoncus quam at, pulvinar tellus. Phasellus dictum tellus eu nisl dignissim, id vulputate odio tincidunt. Mauris ornare ipsum nec maximus maximus. Nulla non sagittis turpis, in blandit magna. Donec at leo gravida, cursus diam ac, ornare velit. Praesent ipsum lorem, fermentum id lacinia ac, ultrices ac lectus. Vestibulum efficitur eget ipsum vitae vulputate. Aenean placerat magna sed ligula efficitur, ut consequat eros vestibulum. Proin placerat sapien sed arcu pharetra volutpat. Nunc diam lectus, tincidunt id eleifend vel, lobortis a lectus. In id orci sed purus venenatis pulvinar quis eget odio. Praesent quis nisi porta, blandit nisi a, hendrerit lacus. Morbi mollis libero non commodo consequat. Nullam id erat mi. + +Vestibulum sit amet ante turpis. Duis in cursus eros. Praesent luctus eget augue tristique porttitor. Sed scelerisque facilisis sem sed interdum. Donec tristique augue ex, sit amet auctor ex elementum id. Sed lacinia ultrices dui, vitae scelerisque leo fringilla quis. Proin condimentum facilisis justo. Integer nulla mi, laoreet vel magna elementum, dignissim volutpat turpis. Aliquam erat volutpat. + +Ut at quam nulla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam maximus felis quis tincidunt fringilla. Fusce hendrerit pharetra pharetra. Nullam venenatis augue eget neque venenatis varius. Aenean posuere posuere nibh, sed vulputate purus tristique a. Sed nec lacus egestas, rhoncus nunc varius, euismod metus. + +Aliquam pellentesque vel turpis ut aliquam. Fusce aliquam fermentum eros, gravida finibus arcu lacinia ut. Morbi vehicula nulla vitae viverra imperdiet. Curabitur eu euismod dui. Nullam congue congue odio, vel varius libero volutpat ut. Curabitur ante urna, vehicula nec sodales vel, lacinia eget arcu. Ut mollis rhoncus orci et facilisis. Aenean feugiat, purus a porttitor feugiat, ex nunc convallis leo, vel semper metus dolor id neque. Proin placerat orci ut purus cursus vehicula. Praesent molestie gravida ipsum, ac sagittis augue viverra vitae. Donec mi purus, congue vitae blandit ut, sollicitudin eu lorem. Vivamus dapibus nibh sed placerat mollis. Fusce egestas nunc lorem, sed interdum turpis gravida eget. Pellentesque enim ante, accumsan pulvinar dignissim ut, faucibus ac orci. + +Fusce tempor fringilla bibendum. Nulla eu suscipit dui. In hac habitasse platea dictumst. Vivamus at lobortis orci, sit amet aliquet nunc. Nunc lacus mauris, sagittis in ornare sit amet, venenatis et dolor. Donec pellentesque nisl blandit, facilisis urna ac, dapibus nisl. Integer sollicitudin commodo euismod. Maecenas vulputate consequat ligula a accumsan. Nunc id risus magna. Nullam pulvinar blandit ante quis elementum. + +Aliquam cursus lectus dui, facilisis tincidunt libero euismod id. Integer id placerat ligula, sit amet mattis mi. Nullam sed nunc orci. Etiam ac dolor sit amet purus consectetur hendrerit. Sed facilisis venenatis ullamcorper. Maecenas tincidunt nunc scelerisque vehicula eleifend. Nullam tincidunt tristique felis sed tempus. Donec nec fermentum dui, in tempus libero. Nam viverra mauris ornare mi finibus, sit amet convallis dui aliquam. Cras ut ultrices tellus. Pellentesque scelerisque placerat vehicula. Quisque cursus malesuada nunc, ut ultricies metus imperdiet eget. + +Proin tristique gravida justo eget hendrerit. Donec id tincidunt sapien. Fusce elementum metus eget risus fermentum, eget venenatis purus rutrum. Maecenas rhoncus pharetra eros a iaculis. Aliquam vestibulum ipsum sed vehicula tempor. Sed convallis tellus sed augue pellentesque, eget suscipit ex iaculis. Integer nisl sapien, vulputate vel congue eget, viverra id ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc blandit aliquet ex nec vehicula. Sed tristique gravida tellus eget egestas. Quisque bibendum ligula velit, sed egestas diam rutrum in. Maecenas hendrerit augue nec elit lacinia mattis. + +Phasellus ultrices ex augue, vel venenatis turpis laoreet in. Suspendisse a imperdiet lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nulla quis nisl finibus, dictum urna eget, fermentum purus. Nam fermentum magna id nisi semper, nec tempor lorem blandit. Sed dignissim fringilla dui id lacinia. Phasellus vitae viverra erat. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque vel malesuada lorem, pharetra consequat diam. Quisque tempor dui eu quam aliquam porta. Donec tortor tortor, tristique vitae sem eget, euismod viverra justo. Morbi id mollis ex. Suspendisse potenti. Donec rutrum, orci id molestie malesuada, massa ex fermentum nisi, vitae facilisis nisi turpis eget enim. Sed eu mi at augue maximus rhoncus. Cras vehicula tellus non gravida efficitur. + +Mauris eros justo, finibus lacinia sodales ut, tempus a ligula. Nulla sit amet sagittis mauris, non fermentum magna. Quisque tristique sagittis porta. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In ultricies, lectus in rutrum lobortis, elit nisi bibendum turpis, a gravida ligula mi id dolor. In rhoncus suscipit lectus ac maximus. Nunc vulputate aliquam laoreet. Morbi eget purus sit amet odio suscipit rutrum. Suspendisse id enim eget justo fringilla fringilla. Maecenas et nulla consectetur, feugiat orci ac, scelerisque est. Pellentesque iaculis justo dui, nec volutpat elit tincidunt eget. Curabitur finibus est a nisl interdum, ut rutrum lorem luctus. + +Nam dolor ipsum, egestas eget luctus nec, congue vitae eros. Donec libero massa, molestie nec augue lacinia, auctor iaculis tortor. Proin tellus mi, vestibulum in lacus at, fringilla commodo nulla. Ut ullamcorper, quam at blandit congue, mauris purus volutpat nisl, quis fermentum justo ipsum id mi. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam vestibulum augue eget sagittis porta. Curabitur eget purus placerat, fringilla lorem non, pretium mauris. Suspendisse potenti. Ut cursus erat augue, quis laoreet felis posuere sit amet. Suspendisse potenti. Sed aliquet semper quam quis ultrices. Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Donec placerat massa et ipsum malesuada, in fringilla mi mollis. Curabitur vel condimentum enim. Donec luctus libero eget dapibus sagittis. Cras vulputate nibh non mauris accumsan, vel lobortis elit hendrerit. Praesent hendrerit ligula purus, non consequat libero accumsan sed. Suspendisse fringilla, tortor in fermentum congue, arcu tellus mollis sem, ut tempus enim dui non leo. Sed in augue diam. Maecenas vel neque eget mi lacinia ornare. Maecenas pharetra consectetur erat non feugiat. Aliquam lacinia augue eget elit maximus fringilla. Suspendisse risus lorem, porta eget nunc a, porta lobortis sapien. diff --git a/contrib/c-blosc/files/text.blosclz b/contrib/c-blosc/files/text.blosclz new file mode 100644 index 0000000000000000000000000000000000000000..3482d788158d9854b83b7e16635897e9d993321c GIT binary patch literal 8590 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 literal 0 HcmV?d00001 diff --git a/contrib/c-blosc/files/text.lz4 b/contrib/c-blosc/files/text.lz4 new file mode 100644 index 0000000000000000000000000000000000000000..53c8e3624cf7cd9c6151fee59a57e471374be1f8 GIT binary patch literal 9827 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 BR|Wt8 literal 0 HcmV?d00001 diff --git a/contrib/c-blosc/files/text.lz4hc b/contrib/c-blosc/files/text.lz4hc new file mode 100644 index 0000000000000000000000000000000000000000..311f7928cd04967db790beff52f6de148995bc7e GIT binary patch literal 7939 zcmXAO36xaD^?u!3x2oRjeea$bW~O_(dlqPx0YR3aB8Z}bEDA1Bd2~O9r#;;>y^N!x zA_jy67c}6GiVE(EyFW255SM5INlZ`_;}%81M8qgjqyOUk`%oj2(XSR>mTXv3)I!eKqyua>edVI(^l8Z?aG>mMh7sQ%hRCb}g68mrGSUU$blV zO42GgeNHv+lzNhO(W#9{9#?ZLePbb6FV-qf-m$C6lHFgoa}oCMMMbMr(8H9RYH>ue zz}mkWGt;v3cFC$H`<7T0yH>H1^-9S~_SGx(YA$Id7ds_qQN5b9D^@L8Ef=h!9?;5< zNKPx4?0m9n7Z$Qd2drYzsU{ce2*$CE`DCSDtLV4~Y<7Unt5^GM{nTpJP8O`bISk)o zck40ix>M~f7v{1trE;m4_2=!PU2&=h*z7(Xhw~mg$m))sM=n`#dUU{5=U?pF5y|<^ zfYn>CHo_g{O5REKT7ypSHpWfXm#kWSj$PWq4s_;{HK&w!3iXnnYrT{o;dOakvn!Te zwe>HHD|)q@V$n(#^(yMQ70Zjtm6}ss%KH1t)w*4=KVhlf@_!gy4`K&fHX=E_Ud`Ld zH`w+4b-lQDk6p{K+G4xj<5;x`?6`hCm(74%!8HulF442C=&95m1mh_Nr!W~{cl25{ z{eTh4S@mbwyaA_X*%>CzWyxOqO$Kh9uE(ui1_A30HruNA)a_%~cf6EO4mbmLMej+` z(mU)}yO}7!!csC{uT*0g7|dXxx|8X3sMQNjX(H-Kr`9XhBBzR7c5OMXvrA5|-m1Fu zJuK2|)l;&%AT;m=o1<5{=KL4>^`;J36=xN&kC9&vQ${4`)SFO_!ejK6!YED+++5Fu z0ck7$HmDzvJqTu&Gam$UYnWPsY(CQUoWGBZ^gY$8buqi#%IE97RjV`;VHE^=Pi3R@ zjAj5E29MS>kjm9$Sz~Mpvfl!m$*0tqZM_bQ>O}!#^py)~PG808by{)CVymA2l*1aHD%NN4 zxIKXURamB1evuB%nvD}Id(uz1aKJ8U_PoW?{q?sPZRfI)R%m|qYGjv>qBcAK67eFx zBBzPc+hX$ICb2#*%)la7w0QmFTSM({CAnOd=` zKM~uB=5-7XGWi@owNL-iI)J>u_)MYhz}UbXkV_bU4>ESoFR;rgm52qUH^Av`u~2iz zNtRY)FgFIxGD9EGFxKgm1kxrl?_lgx){xr`_&suivL!ntr&yJI{X-VdZDJ?{yN?KaVUbIq-ea+Z(egL$j_0@|5PRXi_NY1khe+SLlE?hoPF4k*(R;>v51;a7SI1R)p z2yfzX=hdsV$5Avf>?FQZW>2eKtjyKGR!IC%h@XWRP29slzgD8WS&7?eTyjR~3dOp3 z${u_hTcbKFeK2514}CSL0+*EA5EGPLzNC1C5+aas|2i;VV4c!WiB8yNnM@eav{ z3NeQ9nqpGo?w}GkFjz|N8k|! z$39Nc6c(8EoQl>54R#8^K8a&7*n2LcJGrIelm7gx?Tb%Ajnl)WYa2)}nGU zY3MD|{5e6$f74?<6>GpLSV>LgUgf&@Fln=P`5}zv9#OEEciOcBEMpI@H;j|Hxd7QX zHeU1M--2ytxR)K?z}=jtN^XMIV$O~&7o4(Q-6teouZ(R5{z&2=h;0(f3b=xp;uj(Q z#OB$H7d!dm4fzR8>C?JoG0ZY}QU-FGdp9Yz7DQ1*Y7QTxp*-FuYXIZEjiz#^*++*V$>N?cxMR1Bg!{-@x1K!PPE&fs8lIP_BW=Ccx=TjOJpJ z`t3ly*jKMvH9Ll?2ZfG*IN;eTnk%>{LWnLH&W%5E`4a?d#|xuDvVg+dF_ODWgsqYq zm8{nSe6G&Wc5OQwt0iQX3fHR;wz|AYtGE&`uO%1hohlWULLiSO-|CUu1)Qi;ya5;D z98LV9^Jl@XhwNheZ*JTGM$~|Bu#hiTv`CB>5;**1b{CzV*LZt>{TzRY^7 zr}P|yqroi8v|V_grJce}jN)#2DrBtWQxgATh{xRK70^+wz0dFy9dul!>(!fp`t$3|6)PwEHYkX;$knHF2!|*{Jopkc)L?-@&)T5!9J7=YC7RnDh%#& zX(e3(QWEvKp`D9c1phO`*F_{ZmtdG;3p5BNd!HG{c{<7^53y&N%=ja{^E~oGA6w7E z)={WO4)LsA+mG<6L6`bMxyuB$pgIFNn&I3;FI?>+w+vJ#18($V5676PVf?g-HI!oj zzDImnvU3HE;j|3c6WTC|cyf!0LzOH_{G-50yls}%tN5QlT;h`t$&_84&*UaQ+r_=t zyX6TkbR^%SGWFVPjB6SEM2bf#l*}pgz=<>TyB_hGbX4 zc~YW5aEd9q7z_a|dGMN42RV-cy`$(flO;iJLM;*P^;+I74=Hh*i!48s5k4{L1CLxU zXtN(*^tD^PRUfS~8f)uZ@>0Zq`tVzjhXkge&8~gO_!)xVPx4L$xA~B}%#;t8Nd6*b z>$NS6Zguku4--{f;>Lat2a=ez)Q9iauz~kJU;bA{8y~C%6;Ams^%yMOsb0fe%0go_Q!LKx%!z$$z<{?j! zc#cP{M^vmjL`JMDO}w7NnTj?8Z4kWM4Hd>E!smyWc8tQaSJC4{*Mjsk1!nSZ2hyJPQLe9#F>R9Ny=Wg}DHJWkkKn zeP;u#A=tsG+ba_uzLnul;A=I7OW#R2$3;H|<-dLW0TrIHBnZ0@8x3)~3qI34a0?3S z3=Bp{JSK39$u2cDCg^3s-}Ldt0&f9+Da!6hIJwFz&sKq4128Xy>;~3u)n3+F)TFsQ zT)To)DJENE;-G+phN4SwD0IdtKw?RLCDbbZ~EmI#V z_8H=fK(|E_0~`F|;#MhI72U{iU38Q#NRk101&3do;!eR1Dn27<6>xe(hg~Rm#VU>V z`iTwVd@0^*XOtmhEghi5DS*>0`rLY{KdM~sy?rz6-dn} zX|g{k@XtV>3*!|M8yPzU{(Bpl_ya_8%#7Cifj^rF=8}?^n~YgO{x7#)*If#?E6*=u z{9i8q74rEB(cZ|GbM<_jMtEqs4@a;gv>jY7_-kB5qP#l{TZ821I5nhM&Ru+~ON;+mQp%NBTT(HOmKQn3s`YXzbEMnhfvNI5;Dfoj-SDXnkx{pyFjVDdMMberk z_eMzfRd4pjau<083)0Xbs;u){f;~a**lzg}Bv&XOZlRz&|2MyZjdoyy^`tv`XT!%=JNxZWa81M{Jg9T~Pu5qroSk zJfEozLVOYApCkXq?AH9-;HAyJ&Z6^pGkl4(Tgd;!<-}%wrUqL61w&2rz#9U$Cd4g? zjFn&PgDHkstHjrg|1qxKh>E%^;}rT2yWf?HHpH^!(wY$VDf*G~FO86WsjIDGO*OUP z;ipZ9?NKX;54m}^2-h$2h;M;jibb5OUC~@O;YA7XG#FDEJ?PK$*FS1y5l|2SZf330 zE>nelbZawvOwvz2Ue}h9llD`Ph?cfBz*mZHMpb5fG8iK0Z)c3oVB`s=d!6lZc@Gy) z1Tg8Mp)6z79&UoE9+;dERy0x=&C|7UnBUDEuuRiE(_u6 zI32Idx#W5`VVo3_S2e@KOqQ5=K(bfKygYs{I-JtIrbtI{XgE@M5}Rt1 zg6261iUwhUDGF}ypCrqIyga0CaPuFu$Jqe1FDASJTI&^KI7&nF`W=_JKh;qxFXhz1 z=tjRd!1xC3LQ@Rx)!C#`CvzI>hE5WrVxfVD8`a5v{%|uL85BP={3qiR{q5yygv&v% zy2~&|M#QjG)cKWy+mY{2!K4tK>*M=5zi$X_cA590dZQJ8Ven_;#uFjDSMw%l9IHiM z=g2lX5yH9S-EadlZsY7P$VYqVW5qA_u=6#DL+&eLVq1jf0-dbPO);L+&JXeb;`GH3 zzQr$^P+efk6=~TYf>&Jp?Kq#2)|PSz=K+&;1&|1yjqn%s*F^i&$`;A5G3o6%o9LGJ zNqpGL(@b157w3pt9yMEtF!#$!Y8*$1}%qtq&Ovg6K`NV2~l{T8m)fAIAB}G$2 zdcw>NuH`0efKcudW_*|uQ$vUOGA1BTW)uVMB{m9N>4hs)v}c@4QVKeJu5gHL2-0IQ zehKi;vUm_;xzhz**@Q0vyrJj{f>%=E!A7^C-Q=+zI9lgA7; z_){9EHcJ3xt)8=5<=0t0HY3KTM2W<-$j}+3-e8;EJCd;&$eX}D7WwN5*<;epCfw72 zXAYrNx;kb24<`SL$ptP%*;xNZNH?Jupu2_smxiy%Jtl^;3|(X?kL%wdu08f1f$b8% zLb}6k{N$pU%_0&C>#V(0-j$5hbX|5S@gLC~*})%SWH4NYe4HUZ_M;ovokR_aU+FS#=VFyh zR)AmQVh2$qV{8S|t&-o)`1lOpL#&6%cLJUrDGdOv5MzZp#f$5jL#1=cvxD68P#$8b zYVB^2|4U$>N8BGV{yP*O!(6$@6Z{rad#k;q+1srALx*)nW}HO+l>zQII$X2`+ML3@ z9GM~S1hpH)A&)pK3m>`oLnw3-UmX$WnQ)#ETR4svG#td*cvrP{CRdk6*c}XK7%o@D z2x}L>*%TJbT}OOhI5zK#CT`^DhSo@adW#`4*yiKEVDZx~YsFTR-ObcPQF%%d|E{7= zk*V#H&NbK%+`V@gcE|Y(E_^0M4@K!{P;1*@fuv138I(TD5qbEXStv=WF!{QluW6GL zhVyrm%I_v!Mc#w*hju*53sIkOe*or~t`LjW-&EqhHoiBacAGe!ORtAsVq#UZSk1(K zAKR`QG7awg1zw8u7SqK|DW^Jw?mQ?;f|C4|R`E@9wCB1A9BayZGitWV)_eALNMjgo zKzdgt>i_lQ6GDt^OV?|6f@t?fa{maL-+P6r>$h6asKj6`U@D_ZE;tYg4$eu!bzXQU z#Mk-QiBY|k{6B8voIs{r($3}im|W^dkGH+1c}FrSHw@z+0lmiH7DH{!!G%q7HNu0< zzDy@SCyVzHew@W^$S(`iF3_6yPK%sniaEqb`uHo2cvZWY=H<^3jA=d6%I9Wwb~?2` zw&+?_{4*uaY={-s_}lZjc`o(baP>q`wY6t@>{A*4omb|3{4+zm6&C#&aR}&m24|og z%i&%jpUH}!;xt2ZYZN|9;$&_t^0k%n9>5ovaU->B1(x9Ba?23$O@vKuiWI(VrAz#B zWTzO7x-X>@Ye!1xvhBekP^Cm}stP!w%YA{15=8eHINO722)-cHRHE)cxUj&BfAFe1 zK_2w8bBNAsg?G7{F$!F9@ff4AE;)>(&dML8_zO`*ZsVC&zPF24y6ArN40LwdgNx$) z)JS~r!NbX6w{-iuadiZTkZ}cL1~+aXxy@s28Y=H&{Ap7xPxE=hWYNbKnmpA_>zHwb z5KD#Fmh`{mLjGc>SeMmo962I+^AS0kMC$u?xjv-UgfSnG+H2~8O(v$J6vO!KTG)n2 z|0sGUOK)*rO=$kJ_Z61?(yRL~Tz#bQZvYo~$nmntjdHC}*LBBomj>A$Wwte$H!Ai3 z@M0iZ|0qO1GjVrBe(f@Oh<>SXtd~kC?pEU7K)m$H7@BUX|D}x?9`Ou=1Ff!gqq$=E zbsTPM;fu%c$pKn`4fRtQY~xsIkZW0oonPi1Qv6&&*odCi7o>#%J4iQa=K1N8G;K>! zQ(XQhA>U7h2JZx8RD{3A=))L3nI-CiNW}ZR&^XeVKabI$Lp&5x*O2&6oLYs6>ZAO$I>C1|i?tas-=D2n-!#C9&91itd}qk_e`#@TTD~s1F6>sMs3VAThvFH^ zP=anpp_TXek>=}@6gP#oO?yQSR4jLA(fglPU@YV2Wg=Xw-hM`%6pGrLtJC~o!} z^N$>6hNJnSA zdeJ!fo`7i}+lSN0miVmZ=7!$2Zry=GsV0E@2Qk(t=1AD>iPw9hBIhw)=f_Iq(%TpttTVCKWYdWDcgcgCy(DRMD$#S(unwm%w8_6R)S+#=h!Gzy*i5CN z+k|XC^SuQ$=z~IxifOv2L;NmCANnxT1ZTzhZ)LnNagw%^_k(P3;mLqEcE+8?1pea} z(2v6Bm&Y8H7~C>RCXnxwbb@=RQ)-SH4`%5+@3N^yeQ=Pp`aT%9Fj>>R|JoD7mgSvt zwZERUa=v49U);JjmF%yl8Q(uTSzk0bc2v1q`->mm%UxT_+kG{CqOjzEd#q)b7x?Kn z-dOQpobU3d>lK=8xc%6ka1V{5&l7fk4M)v2{_rM;h1xYhvVT~CZT0=Mi<1IJ+VfdlM_4J z!?kq@_A;<^OU!W^X{nc{55_M5^SqPBz}D&SYXXl}VjEN$k0bF+OW ze@#lW-Th@;4)5lU$u45<$F)rc`AG+jXM7usJ*AQ_HBBj5wes^4{>&qbj%i5sl#Xh< zP9OVO72OhY?sT(XqV7}clfZnZF{<4hq83sGUUVp0@=-^i!rB5i< zXVjP6oRXGjPsUk3waApEvDh`TE1#QTWOCp*lV7ng+aF_Z6b!EM|)Z+OPe zKO57XSb0yH?_=^^tvLoiqPxqGzDj^7>~pf^b&e*Ufe)T|{2iEjNz#PYP$*R^+Gp zaS0bv1iN&o@uW(;cHuQEf@HFE1%1bx0q9v+f6tL%p!80czMNf{BB+u`97`tyBVu2X+($8|d2 zFZXrY`aZSmvd`1$e%qGQzU;?3wR3;;?bL7Y({kzi%k&?6Z*t*zIxhR#Pkq^@+w%Ld z%=*38OS_%rKDWMIUZy|)(P#XnEvMzyw(0TF)@5JYbgZ{FJ&yI*=BZ6@{nlTPZCcj0 zPuu<6E^>-?dYS%ozb&U}Th4m_&vv==ZF-Y?>Q$#{J@!?O@`;E1D(T6 zL&zjITaLb6?`PiecE9< zx$ts-y|24Sr_X-ew_{lslks{tq0J;A&dcX zz~$CO%s;8@Zh1NWySSVw>JyTR+4pXSm!p2RwVNH(#oYie(|;Z28tE_c-&x~@fiava z^dQ2*uEq7$Rzr}eHfB9b@iX-*hNOG9`as*ZrjKr?({bI}&78^XaMgRtjZ~4UDzV~v z-{d~GrVgYw(O^2-xtSZfoO-Tf)nF3+i<_vAh(}4-BGygdP|(Zt_w_FFrIYIU^yJq* z?aTF{A&@>MCsEI{gZMw>4j*k>E|)`n0d69KnHi^td|!fM+2FqGRsNw~`p-ok|9V_h z3G!nHp2ym+-Q44?9Vd%&i^a=ffwUB_b2Ew`y^4@c{i!YgHhBN%aue(97AP(gV>ppP zbbJ*Tx(9m59|JItH4$Evu&jPU(cK&fWD)W*{kuG;-PHZIU7TSTJr?*BY4&A3AJ>FA zGx2Y$d{)s<{dqW%iJYPPkoz?{yg$RiwD{*|Dm0(twza^3$!-JM^i*WV12I~BrHfyQ zBhKbB!{eY0ezf1J6A-0_3slN^uq-^^aX{2g3$*ogn1FLRB|uP5G@?3lPJ%AdS%l^j zc(}<-nd%Vh$Ni?kWg$lV(^CK}+{NdDd+tIl9OJUw#{}@Xd~~_2=?7F|jUsV~$8q`W z61oD>Vs*9Z-qle)@0VkLv>m#Jq`0IiHQdR3`M-~C50{M}0Vh14ecvPX4u;Sq1b`3t z55(|TFa?+wW@Zj}n!epK1#6%vge#!OM-lmeDOIv*rv~QZzKfnZ)h$3{+Sa5jaej?w zG0)j?=Qtfs?`gu3GA{9)t62a59P0I-crs8m!xA|AxdWQ$A+8Zo;D>o+Wegg%2mtYd zu1;|73zH~s@K;UUNAM0+r=>bL&DCzt09Biq@#GT%b@$aQV9z$XAs*k~M0<%Pm;NdN zJ>Y1<-TT@;yCem90+Q|Mdg(9AJ)CupbZt?lv@Zo-zQ{^yEDdg@SBW?k?o|fJ=_a$t zW(sjdh>GOD`?=rYvXQfpk}HJ)_JzqKg(2Nq3bd@~o(Jidw>LYZ(ul;khX`vAz=tep z&3X_|%xaLp+MR(WW^gBQ5D*2F_AhykIUoOoA3blisEMRAyXET>l_@Dm;eT7!BT@ct-l+L*2JLs5bik> zqMK*i>DVrzW2IRF%Vz}MO>47<($fnme z;}s}=@F7^Ik^=Mj(|r}~5H~XbjQw5uyTvi80?$q6HOcdjTCj;MJi4iGNH>BLkWT6$ z9E`L$b!xoT%nn8qjD0Y{wioH(<1^Eu9u5!0zbfr-p6d)odi9ZtJ0@*2XsPFq(#!Hn zFp?zk9&_rXgy_Q%Q4`!QvA>ol8buY0nw~Sp$hR#@0-9YQ0QpQru~eyUmH9%Yt3`w; zDX~Bgo;C87B}JN2qq4d8s1%F{V-N6kV>u-DGvF8AQ5?$|(Cn3yu~DXiO_hfV9bpDm zBoXhl4L+ADGtWl`Q+Q>_Q2%;nh5D%wHe|}oH2`yjj{Za+@74?wmdQ%JOYWLgx`y=+ zlstJP5{)N!F}Ta&Xp0;dcr^ng^aNvtnje7rjSECS#q!XhO!2)NBS1#5!YX4HIAC-t zK4`kp|IMg%BII2wOzb38s-N^y0PwLV#m>ARPm_Fk^mM#ZdW-XzVDh0Xm`y)53XvHb)a}t`J)*1* ztoT9B&%BXhMAyRfFjwvglH=ssDlJ|f<#cTYX(rV`W6VcfTvpvzhz!IIp2OQUqRC7~geQS6`GuC5E=yBo1fCR_wgOtCt z;8g8ZGajmb%u-tD9comhYkkvHT?5dfJNsX*1aCu@SpHGDhD8?6ESRz`@Uz37exUBz z80?a|&2raM^*8Ucm|KGx1g|tRgP}(cj5){pK!MmGY#K12fJAVwWgTT<8uz|z7*1Y? zWpbTWF{+FOKTyY}1fWcesNEr`bej_sg7TmsapCYOu{awXrPi1oN8G(qYM(#YV<;Q~ z0%<8J8cdmdFq)WATA@<)9ACO#jAh_QJjEG5U53is&;wv?6RtqTu_>W;jlOqZqFW@P zf&{N_4*Rh=S(4`kHRXHrI7Q%df;}-RIawehK#y@K z&Cs(3tuuZbw=J!}!5C_4)9Vjmrz&imKvXn^8MeJlQM~sBG8}cd+}b87k!nKl#QNH^ zu4V0qI!M#hWdNHIFQ~b&aeQ0+FM0Sk5pB!loec&JWvzepR4^!kzM@#1Q0@McIaOG4 zCjU7?;Xb&SEkF3Ut>!9c`*H=-I&%1PZ`s+dYujNurbHTnrFqY4ffXn|EvF{}dmKkk z)Ej2je_%MEg>>ve0iC-BT$-zqvZb<_i>%`o1kxC$iBHYYrSr;#6ipoqIn**4G-AD4 z9F#6rNt-7u&f=adWM%=_N97gj;-s*ITlG4{tfzjO3kAg%=eF8U4qEvn;RYe?t)KDP zH}VtCaY=aHfMSG9i0&t|7jUNSn2)h*(h4V6S|#Y1%|N6v7Hp2!@K7@W5j^O-p$4o# z*Fx>`JPxz=raVqb*cM}H<{{i+>~9mBz2J#7rEOeOoRb-MoqSY5=P0b*(Vg?jVXQi1 znW{7|rw8WjMD}d@XDW2f3Tgy>DQv@_9J7naQu0x+GL1k)UI~f4nO|-bq*IwN9;mv@ zEX?looz-0Pe9PE(va^7FS}885mSSMaXOT_yM1T+AdU)j#R%Zjdla0;+7??cRETrCo zwYr%M)J1es8URLt%$q>#N=eAstY$1$zn*jufW*Ft-&>4uEZ@^J%i~;mm1zFtQ@k-x zF9M^7pFtWu3-CjJQk9Kqos|$aJctJCnGvHdVtH^JGaEJb>!A{nqRyWjj(uz#1A}Nk zp%KqCIv~n8+NiCh9LCyd^JzoEQy3c>P{2Rm`0yU07aK}&f~Q4`5S_9!opyg}oea|j zHbsn91;_p&o2inr0aa+Zr~bpO!&Y}dCJV9%8L$939f)mgYM^Rl)6YM~x;zAS@0{ZU zW+$IEF%^(B8%{*O_!HBrQcL@^mTi*|A3}QNtbW0m$hxaothNhdRiZ}@Huv;h4+|QW zmmcdrs3c7t{Irbn!5thHYR?LB8rWSC1SKI*k(!*HM%%G!aH>ap#lRHrrH7_E_OYH? z(9y!AMwv^8F17ngI0-uUoOF7@9QYMB_vin_}5$ zh{03{l&=OvYcP9Zs)~_;(K<68zLW)zXd$6?LCYZTMXelrA~dQnOTq8K8gmE2%EYlH zq}>^ItX=v3B$#|oT;&)J2DDU82(m4XFK5Oz0IY(r^y#{c^TSkW&c+%me9ZAG;2O_Q z1z`UDJX+6wpf3%@-bA2H^bo&|El3SZY>I4qG0ch99UmqElHHU*WNys0ssjYNpM5#ksaKIsOZqvfl_L z%TK!y|K}bY4JK*Z;uQHm4OA=}bx*8T8N+pF4ev|E1=BL5jerGiQd!ZE-}WK0O83!r zR_t$MC$K^y0#1jx&(v@(Xahp#_ym`ii{`HfyQAvXEkFg>69+Nk zq~k!@0kAaSQ~a3uTAST|Vhk$lu#!*V7FI@O&V+X|wI8+c2WH1{by30$6do|My)#sP zkqGDLxzvuc83fo{*rqi+zigqvDl%(qc~PqM6hPBSf9)=4BEVDJ{j>H)Eu=*yAI>d+ zoXWX{DaXNys|#a;w{%3C)&966=-XAk4r?o3aAE&Sv)amNghsHb02D75eTs!;w5XvR zx{MUI(_EGbZO$0OY&%3OdBkMpoVECAPD)u;w~JQGhL}2*&xcUGWv%EmCwh9Za1?gu zZRTJ#7Bq&_Sz$-_pEt_a3IJRYOSQrj1TkIU_!XC|j`PH3(U1en5LSn0XAb*WeCN7g zcnm^Tm4G#g&~wfz4RYq1Lc1&-vvV0FgSZzg-Wg%C`*1t7f#wO zw0pitV4d(dF-c=WMWS<%EN|Zy4qWH zv#7HCQwxbWw+;MV#a`I?7j*n>IPGuQc=Ld6bqx2k5XQtM4`iDftiLYLiQXu|_vp7! zzXoT^UQ22&-*AP4t?{g?a=YUT2CDr+mxB#%ENyWIlKZqHc-tY4-JisUmJ3$kD4PB> zmW7N}HIXBo(YQr?5aHw8SV)ShTCGPE)=BEv*EoVbJZ)V4WG#bM4P zmf2FQIy^IMAgX9;q;S2sWOO`f6(8M|o=8;Y^{qPATzR3#WywU5L;&D<%CudnSBqN5 zS3ZZ7oh@KzVsXs8C{qWL-R3)SS=#HL#CFqRk1c7Ai*4i6A>uj^a={4C07Y>LZ#w^p zgkqJ=QJSq$D^xeFB8F`e+_)qXZLIn(;+(hF)eV4Y*=2d!h-lfq7zXankwA_{@9XN*(AEe3eVR zEzQC0iG_=EW6Q)&n8Tp2d}#1I#Srd#X2@#-<08g;UCh;;!kCo(@h+61MxcU&r6yBr zCek%j)*o%ky7PT0FZ0lgqI<&v7c((yt$R*7mp5m1dJ&dNV(4!I*)p&S=g*of*pSH{bn13wm)t-2R~&CZCX_U>S_GOBahholD*|*e z7tY@8@=Z2!YQAHO5)bP&T$(BCL@3sLPLiyia(XEi4aRsuYIK}<{c#*e7?;FV;EEyP z>@xy!)UrrvQ_<(CkXdw(eYV|)5^6ym5%(N4vG(E?DUw2gEiwBXXde71bb@s=gSgKwD8W*;IhS(VUxJdP53v}qL(5P#&=6z7SO)9u6NRW zsF$==r0F2&>Ra;ECTT=@!rITO2u*C>LaD7j@SM&Kt@4tM;m$a4WAiK4nTT;$v$yj^ zc>*&|06&-Pqsis~T;5EIEfpWx`^@@1$WLh4`guQ$!-su1e;M7bMh4Dc9~Nlxk(FKHSQcPxzjfBm)w z(Du51lueuGz%G4>bne`%DZh0En;iG@W*^_S{wWH7-?N8EbabUK^@Flg2u9TZazDk| z@}h>O^WU)x-IH|u@z$C8uGf^=i?-~*-gU_vYMW5ZwMVg^%5=h*@88^Vi`)&#s;y4c zXdR}Fd~1t-W=4wN;h3$u?-N$Dg5B)#$B?~p`2Ye&kZ-Q_%5qr3s(>AWs2 zMOav`jPb`^cHjm#C>0QGdCsQr_xPOwa8)pjtiAQ6ZtN!6x>Ao^h$^jobfhc%-8O1Y vWIjw+e^^5-5#nz$c?&h+x#|J64;hlp3(*p_sW!hAX7Ot3*I)hzQ8PeM4IofU(l%hS zyS?=`v-Is}qw+n_5hUW5%xYqqyBrl`g+0*#(g4l?tH6r9>Lva1I%q_G6M8q+2en~K zw+XY!Jz#}OUfxtkUY9Z`WVvdL>=3$vUk6ik`W6C!0006M1{Y0n&kbI!pnqIidUDBb zmdZ_s+<_I(JQ_b%&h2~kI`YfN1{EHa@@q>Ucg&eT=p(lQVriG|GIQjE)0<5KmXMssoP&(*(_Zpm*pZg@uH(g3CPZQ7R0ag@L zxO^n16=QW&O3K(K4vj_x`0nV2HReTT@9^|>SP`w^ns%V*w0y9l5%OW?79r;$5Uf~p zN3z-zUM&PGj+@*(y1BJ?ZV#8TRLiDbBjIIQW9~Aqm^-Go46OJ%#PqKDg&Z1}>|4N! zbhP@0TT8$S{8_!pB-a|lJKh|!Tg%81Gw3z;PHz)VK0OkrR?(yPgm{}MUo7|({@k3o zqwmp6E321Ed_@RL&X4iC z3FBz_SjyZ0E6hO~ZZJi6tLde5?TQxb=^kj2k{J2vO3?(p)r6vqjI6~RIt~Y`AGJoX zBC)zC=a=0%a?4Q5hNFW%+{k9nN?HZ}xs^j0jz1->ng9Y6=pUE2j;!|h2%qrR1a(vr z%8hNDg5+h`m@Wp@hQUI*BcCsmhgQB;*Bvt`Jv3_pD!g(n&nGVDRv;WHAHq`UVS}2= zu$#E0^lTHUZuI8VjI7uGdvIOs={!k%UJNG|&J7kD#T4W(OqMWP3$$nG$@+q z1-)aWG!a=9UNNBK zfWSZjLWIUNi>V$IfEGQ7h>Un{;nh%;1ORmO2LYOcOlLdCqVgGLB}xC&c|jJfHmY|s z6_(6Riiiqpf@Ag~_HFbZAd}RoE$HEgEFt}qpnng_05WX_g97YuGpY2 z8nHLFMa|T1A6t+0m0bQqUX*sLwQ6vxU{q3}3&Ma*JB%VCYatX1g%k2O(FsVd_j9?C zs3T!GKCeyM8UXs17TSBcChT|B z`U+j)htEE2&@sz8rx|jW2|SkP#no9^ytWjjV7-ZGK=9wfy98lKPt?S@j>KE0C#{TX zA-8_oJ~OdGudN7dB-#V<2d+-Nq72Nxa{_H50Ol*1Zh6rrvb6MdsJTyK6gX~=$>8Fy zVE>X2Nk?5QhglIrDXC*jU{4dbWnfx zOZg2(6>cdlR#)OOK%=-Ydd?^N*=J~k|G9Ytci_a-u$y|u9j*xK64N_V#YNAq`uVE zgDPHLM4VokH^S#|5bz4q{%bt6L&<0xv|vjw>Xr6TJI!w!m`4;3t0JhqBSOj*DiQHL zK^E;JHhyvJe6=34(vSRRVzK%^?^W5mB*qNeoD7Z9i_`AgW#Nuncw0}eMW+X;Us}vv zcmo7!_r#E&BHp7@iu2rbpxQ*}CohL|)jEQn4KL+-op!NG?wa6WN%{dj>7LK<>*+gE z9xSLSOtuVY4np{ea-wCc1n24)14Jyk_M;$;_j+s?JV9uqWQUf{IPY%qKMUSjqDVJtIPcIQ+Ce5~NT?cVdD+)=%7Hbf9 zDvpH-9Y(*!tOW~w#R!j>Qx*|LK){JVMVn1j`7c}~T*+=mEtN!^S{{c5GM#e71Zgbe zb1AKRRDKtNExcb{oHxAK#|l_bMK-1~FtxVNx6f*|oLPInpDC8O6v?(xWAhY@XhhZ( zb>6-R%y(yw4y~s&SuzP+Wf4A*3A9*k=(bo@QbL`R;R8|a?Y+tzvQYz{T=3>Iec*)V z>J||hE`)xk(Bxdrd3e`|1+~y~WC-mp1I!$bSI=ev^*u9;_H<`LD*J6=o$-|K0MaLe z>f-uZr99kiv%lNYtHBj2!l|hEe2*K0HIgh1?L4x+k4K_D0Q6*Te@DL%K8dE}52^IZ z9&JP+D1^CItJI+qb~Ff}EVI~?Lms_@dJtr7L)O$B@6*Y;B_&bP{W9XLGw)8mvf0`U zz|Q!`mjua8)I!4BC_=}8yDrGAwH4%+U;daOwxn?3!DO}exTJAF0*;ogE=U6#6FL`( z1)73=#ShkOd z58~%F2=lt1tQegWLI$~arLmleHgy1&i0x2=US*&lPq*nL#@kLjL z=m8f&3uqT3PzZ~m;frz98l+K^^^^cN;BDN*|McDtHM*I_^#hn1TP#@=#+B6G_m%uu!R-eCEWlDp=r|_4 z76P$r&7zXuWBuK042Nq>EJLvG#ORJ$CpC2upMMo%i&PO#>OYEnrNhS!_UdXyy+gvH`qL32VZ?s+ zqNLV!_o9rWI7N9JYour$9`malj0V+g0GV!?L;Q0Bp-pTsZ9pUQFv)F*9Gw3Hb(AiD zhvz@VcS0QymgWMxLP56A@h<(^r}=o^?@@Rm4^)E$%pDy&Pfszy`yU_$^0FfTqacQj zHS^2?$Y5t2;yA#n?)wYWS{j^J&2gB@MUF03P_yWas}`CWdB9PLIRkk!X=zBwBNF5I z+X*)g877E)uipngCTJMQvSqV0XY8JpDy_T54&A3hXugO<1SxlnkQ|yJVCO|*U2$b> z<*@f|cR;1qUZD}sLLe144uZ`bg@((C)l`~g#^6J2;PgvtB;Mp9{MHgk;NTZ-O|VGQ{-y#C?FUvAiw8)Xro)V`kcydjU3yERa?*g_JJi+ zLaQm88D8NfbAYK0)kNw)c9=%EeO9vMbOJ*hN4-=YGVN#WYzd5UWZ>7=T{{Zd$CRQO;F<3c$|HZ7u zNxg?$l*EUJE|pagmhxX_OYMNfqlK_Qu|jjc^i=-q@syt+Zvlil9JgQ0n+2+2Rap*J5`- zJ?2c!Yc2e&=7y|15Ee!>61&b?WJLQde5e5)pIq>ctHmV#Z1RBAldVo~{wRzy93KAz z5tewC0nciMKig^H6WRd{q7#6hi{J7;zuv5|9+KXNWi(J7DT3G*`EY%F0oI?q6UpzM z>B|<%wc~pN1;9i8pKES;(PctS{lxD}6Yvy6q0o0+XKdLKVPu-?RP|rH&dK5cMi_^# z?{ESCaCZzm{jhDSYE=j=dLM|L;yO-fo9@8}hlWBtm#Uqf`!jvV4Shiuy<5{iuDZnu zi^Ptdr>~Co~}lSSuYaN6LvfvhQ&V#PzQzM==rc)5~4%p`&2W zU+@pjdXfW3P6_>AD!7v?4~x*$ShqOk;mTE8*eA35>cIEXBwT6sxQbRA+(0wvhE=XW zo5e>va0xLD$UU)^>R=0$GEGF1>c)R`17~e>xS7e+eq|bsfsq&z34@?%rgc%-@>v&_ zCUOg{Vz>gnP~n!MmTKZS8Lx4ESv-C;?Opu7uS#;ubVQOD5u+tQBYK`HRBz~eESD!zQ%Zl z|7HXXW(jVv$OQdLQ|QN_ln&$S9+J;yj9~jlu?=HF$Afmj`^|Oq5h%)S+G^OV6*3VMN;!X~QJS78*1@*bY7jMfh#0dakZ%M>4)7-~xT2(PkAJ~qoUI$8 zth(9cPelRDb_kGh{9x}bmZzaqpxd)ZtA92wZZ%Lyb4;65o3|j>o&UkC(pA;zEu9^} zwPjk1*7dWzuP+;>-6984!0FuI-r!ABigf!(>;`*3pA>0{fDgFU*RC|{q2b)v58q9*VC65ie4Rm7qz6BLl24nKCod}mh254w*t%)vg!Wb?yT&c6 zSB%<+D#$MM{Ul)AiddYx;2e9qiHH6hxR*lcM)~&F?KL~#ogk|x*W>ix0&OEESxf)f zirFdlnGDzMa}SI{M?A@G@)a$7!$M-ufw9FZ0$z(EgNy^MH%VbG`*o}cG`4~rfI*{C zL;HhKaMQM#uQXja$R%~C0%)rSn;ISO{0V$P=E8xJ*|FZA05lI_=|py1OytOp0}_`; z%FrAI+?C(AAwJ?l8G|-UaMG>pwy=joHMy+36?&F0*{)Gkh*-v~gIz zCIvT+fg(`8i~mK6a2J`yonD(V6{<7%mpxdP+2C|{JE3i|=S;KM{-sXG<= zP>5%d&$V&YYiYsn+4Pn~EtW%T$JNx^!wgtK#2};h1Q~7HCk|bF(`cC;+C2A8-o}HF z%}=aYY_*@?#^1rF8z3V`wX5_`26`O4ky6*ws2~?yCPXfR3GOY^k@fDH6(t67W^SFE zSn+pqA1~hr5|P*zwr9r6<*HRM%*oV)XGBUix6s)Q*C10}_0}r6R4S6*!_!@YJVqt# zmV#fC4A=7_6%Y5wofAxnqTZ$HZ|(5F34+G`oNR{&HRNiiSEDvp`PHSdsBuyax5x>J=9fSU(#ik$ zlqeue3$rtdx(q#5biwt++;IyxtrewD+{z&prKA7?=y#Y|f${}1&J=^agL#H@YBc-| zj?YES(xl{31$YYs7sgLUL7}T$Ngh7S`A)~pjvezTAqlL5*s}u@l$_{=O6mvA2in$k^Ea`_IZ4YWcyfMz;lT)yuFO8FgoD&DO20CyxU+E{`a)lqL2AB8SC)b%gH1FbKR{W~4hv#5f%8 z#-x7PgO%@63BMuD_Yn{X?ztN^P(_uf3V#WDPt1N}BcED^u=t)3qlCc(y>~OVXG>T` zad5*Sl}x>E7W>XH$2V?|l1p>0hQ!efRr3C@+og<4u^hAEToiNXIMEpae)!c;2-uL< zA)1%*8NbK0bp==YMA#yFMoF>5N@Q8>4)U!ui30)+$OJHISgOLdK(afPR zXe7FULbF@|Y((J?O0g{sM9cFmZG~6A({gq^Dh8qdN+Cxw(hBJX$bMJQ%{!eX`ci=y z1dNUz(PDB98_<+-=*ZDu>y5$EF#m#(tn^uM2iom z4?#{(RiL*;s_aw@1V307%ovEMPdW3f4W{VTN8Kh=G>Jnx*Ly!roLC=f!zQ`TLPbWX ziA#ztk$9I;#uVPGyI60b$fo=B10)>BvX&*bhF6bIL_L&}ucJR0ZchtgWh{;k>P#~u zfO;SZ4dz5ri`xo+%5Aib#rn~Yd!a0x9md^bzf;ssgtiRdGtptX5%-6?otU