diff --git a/include/tscore/ts_file.h b/include/tscore/ts_file.h new file mode 100644 index 00000000000..0d2ecf913b5 --- /dev/null +++ b/include/tscore/ts_file.h @@ -0,0 +1,247 @@ +/** @file + + Simple path and file utilities. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include "tscore/ink_memory.h" +#include "tscpp/util/TextView.h" +#include "tscore/BufferWriter.h" + +namespace ts +{ +namespace file +{ + /** Utility class for file system paths. + */ + class path + { + using self_type = path; + + public: + using value_type = char; + using string_type = std::string; + static constexpr char preferred_separator = value_type{'/'}; + + /// Default construct empty path. + path() = default; + + /// Copy constructor - copies the path. + path(const self_type &that) = default; + + /// Move constructor. + path(self_type &&that) = default; + + /// Construct from a null terminated string. + explicit path(const char *src); + + /// Construct from a string view. + path(std::string_view src); + // template < typename ... Args > explicit path(std::string_view base, Args... rest); + + /// Move from an existing string + path(std::string &&that); + + /// Replace the path with a copy of @a that. + self_type &operator=(const self_type &that) = default; + + /// Replace the path with the contents of @a that. + self_type &operator=(self_type &&that) = default; + + /// Assign @a p as the path. + self_type &operator=(std::string_view p); + + /** Append or replace path with @a that. + * + * If @a that is absolute, it replaces @a this. Otherwise @a that is appended with exactly one + * separator. + * + * @param that Filesystem path. + * @return @a this + */ + self_type &operator/=(const self_type &that); + self_type &operator/=(std::string_view that); + + /// Check if the path is empty. + bool empty() const; + + /// Check if the path is absolute. + bool is_absolute() const; + + /// Check if the path is not absolute. + bool is_relative() const; + + /// Access the path explicitly. + char const *c_str() const; + + /// Get a copy of the path. + std::string string() const; + + protected: + std::string _path; ///< File path. + }; + + /// Information about a file. + class file_status + { + using self_type = file_status; + + public: + protected: + struct ::stat _stat; ///< File information. + + friend self_type status(const path &, std::error_code &) noexcept; + + friend int file_type(const self_type &); + friend uintmax_t file_size(const self_type &); + friend bool is_regular_file(const file_status &); + friend bool is_dir(const file_status &); + friend bool is_char_device(const file_status &); + friend bool is_block_device(const file_status &); + }; + + /** Get the status of the file at @a p. + * + * @param p Path to file. + * @param ec Error code return. + * @return Status of the file. + */ + file_status status(const path &p, std::error_code &ec) noexcept; + + // Related free functions. + // These are separate because they are not part of std::filesystem::path. + + /// Return the file type value. + int file_type(const file_status &fs); + + /// Check if the path is to a regular file. + bool is_regular_file(const file_status &fs); + + /// Check if the path is to a directory. + bool is_dir(const file_status &p); + + /// Check if the path is to a character device. + bool is_char_device(const file_status &fs); + + /// Check if the path is to a block device. + bool is_block_device(const file_status &fs); + + /// Size of the file or block device. + uintmax_t file_size(const file_status &fs); + + /// Check if file is readable. + bool is_readable(const path &s); + + /** Load the file at @a p into a @c std::string. + * + * @param p Path to file + * @return The contents of the file. + */ + std::string load(const path &p, std::error_code &ec); + /* ------------------------------------------------------------------- */ + + inline path::path(char const *src) : _path(src) {} + + inline path::path(std::string_view base) : _path(base) {} + + inline path::path(std::string &&that) : _path(std::move(that)) {} + + inline path & + path::operator=(std::string_view p) + { + _path.assign(p); + return *this; + } + + inline char const * + path::c_str() const + { + return _path.c_str(); + } + + inline std::string + path::string() const + { + return _path; + } + + inline bool + path::empty() const + { + return _path.empty(); + } + + inline bool + path::is_absolute() const + { + return !_path.empty() && preferred_separator == _path[0]; + } + + inline bool + path::is_relative() const + { + return !this->is_absolute(); + } + + inline path & + path::operator/=(const self_type &that) + { + return *this /= std::string_view(that._path); + } + + /** Combine two strings as file paths. + + @return A @c path with the combined path. + */ + inline path + operator/(const path &lhs, const path &rhs) + { + return path(lhs) /= rhs; + } + + inline path + operator/(path &&lhs, const path &rhs) + { + return path(std::move(lhs)) /= rhs; + } + + inline path + operator/(const path &lhs, std::string_view rhs) + { + return path(lhs) /= rhs; + } + + inline path + operator/(path &&lhs, std::string_view rhs) + { + return path(std::move(lhs)) /= rhs; + } + + /* ------------------------------------------------------------------- */ +} // namespace file +} // namespace ts +/* ------------------------------------------------------------------- */ diff --git a/src/tscore/Makefile.am b/src/tscore/Makefile.am index 1045dd0a001..010ad78fa3d 100644 --- a/src/tscore/Makefile.am +++ b/src/tscore/Makefile.am @@ -208,6 +208,7 @@ libtscore_la_SOURCES = \ Tokenizer.h \ Trie.h \ TsBuffer.h \ + ts_file. h ts_file.cc \ Version.cc \ X509HostnameValidator.cc \ X509HostnameValidator.h @@ -275,7 +276,8 @@ test_tscore_SOURCES = \ unit_tests/test_Ptr.cc \ unit_tests/test_Regex.cc \ unit_tests/test_Scalar.cc \ - unit_tests/test_scoped_resource.cc + unit_tests/test_scoped_resource.cc \ + unit_tests/test_ts_file.cc CompileParseRules_SOURCES = CompileParseRules.cc diff --git a/src/tscore/ts_file.cc b/src/tscore/ts_file.cc new file mode 100644 index 00000000000..3467176c2e2 --- /dev/null +++ b/src/tscore/ts_file.cc @@ -0,0 +1,126 @@ +/** @file + + Minimalist version of std::filesystem. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one or more contributor license + agreements. See the NOTICE file distributed with this work for additional information regarding + copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with the License. You may + obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. + */ + +#include "tscore/ts_file.h" +#include + +namespace ts +{ +namespace file +{ + path & + path::operator/=(std::string_view that) + { + if (!that.empty()) { // don't waste time appending nothing. + if (that.front() == preferred_separator || _path.empty()) { + _path.assign(that); + } else { + if (_path.back() == preferred_separator) { + _path.reserve(_path.size() + that.size()); + } else { + _path.reserve(_path.size() + that.size() + 1); + _path.push_back(preferred_separator); + } + _path.append(that); + } + } + return *this; + } + + file_status + status(const path &p, std::error_code &ec) noexcept + { + file_status zret; + if (::stat(p.c_str(), &zret._stat) >= 0) { + ec.clear(); + } else { + ec = std::error_code(errno, std::system_category()); + } + return zret; + } + + int + file_type(const file_status &fs) + { + return fs._stat.st_mode & S_IFMT; + } + + uintmax_t + file_size(const file_status &fs) + { + return fs._stat.st_size; + } + + bool + is_char_device(const file_status &fs) + { + return file_type(fs) == S_IFCHR; + } + + bool + is_block_device(const file_status &fs) + { + return file_type(fs) == S_IFBLK; + } + + bool + is_regular_file(const file_status &fs) + { + return file_type(fs) == S_IFREG; + } + + bool + is_dir(const file_status &fs) + { + return file_type(fs) == S_IFDIR; + } + + bool + is_readable(const path &p) + { + return 0 == access(p.c_str(), R_OK); + } + + std::string + load(const path &p, std::error_code &ec) + { + std::string zret; + ats_scoped_fd fd(::open(p.c_str(), O_RDONLY)); + ec.clear(); + if (fd < 0) { + ec = std::error_code(errno, std::system_category()); + } else { + struct stat info; + if (0 != ::fstat(fd, &info)) { + ec = std::error_code(errno, std::system_category()); + } else { + int n = info.st_size; + zret.resize(n); + auto read_len = ::read(fd, const_cast(zret.data()), n); + if (read_len < n) { + ec = std::error_code(errno, std::system_category()); + } + } + } + return zret; + } + +} // namespace file +} // namespace ts diff --git a/src/tscore/unit_tests/test_ts_file.cc b/src/tscore/unit_tests/test_ts_file.cc new file mode 100644 index 00000000000..e7a49dbd3bd --- /dev/null +++ b/src/tscore/unit_tests/test_ts_file.cc @@ -0,0 +1,69 @@ +/** @file + + ts::file unit tests. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include "tscore/ts_file.h" +#include "../../../tests/include/catch.hpp" + +using ts::file::path; + +// -------------------- +TEST_CASE("ts_file", "[libts][ts_file]") +{ + path p1("/home"); + REQUIRE(p1.string() == "/home"); + auto p2 = p1 / "bob"; + REQUIRE(p2.string() == "/home/bob"); + p2 = p2 / "git/ats/"; + REQUIRE(p2.string() == "/home/bob/git/ats/"); + p2 /= "lib/ts"; + REQUIRE(p2.string() == "/home/bob/git/ats/lib/ts"); + p2 /= "/home/dave"; + REQUIRE(p2.string() == "/home/dave"); + path p3 = path("/home/dave") / "git/tools"; + REQUIRE(p3.string() == "/home/dave/git/tools"); +} + +TEST_CASE("ts_file_io", "[libts][ts_file_io]") +{ + path file("unit_tests/test_ts_file.cc"); + std::error_code ec; + std::string content = ts::file::load(file, ec); + REQUIRE(ec.value() == 0); + REQUIRE(content.size() > 0); + REQUIRE(content.find("ts::file::path") != content.npos); + + // Check some file properties. + REQUIRE(ts::file::is_readable(file) == true); + auto fs = ts::file::status(file, ec); + REQUIRE(ec.value() == 0); + REQUIRE(ts::file::is_dir(fs) == false); + REQUIRE(ts::file::is_regular_file(fs) == true); + + // Failure case. + file = "unit-tests/no_such_file.txt"; + content = ts::file::load(file, ec); + REQUIRE(ec.value() == 2); + REQUIRE(ts::file::is_readable(file) == false); +}