ASR_BASE

Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/external/subpack/net/nginx-util/src/uci-cxx.hpp b/external/subpack/net/nginx-util/src/uci-cxx.hpp
new file mode 100644
index 0000000..4598890
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/uci-cxx.hpp
@@ -0,0 +1,474 @@
+#ifndef _UCI_CXX_HPP
+#define _UCI_CXX_HPP
+
+#include <uci.h>
+#include <memory>
+#include <mutex>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+
+namespace uci {
+
+template <class T>
+class iterator {  // like uci_foreach_element_safe.
+
+  private:
+    const uci_ptr& _ptr;
+
+    uci_element* _it = nullptr;
+
+    uci_element* _next = nullptr;
+
+    // wrapper for clang-tidy
+    inline auto _list_to_element(const uci_list* cur) -> uci_element*
+    {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast)
+        return list_to_element(cur);  // macro casting container=pointer-offset.
+    }
+
+  public:
+    inline explicit iterator(const uci_ptr& ptr, const uci_list* cur)
+        : _ptr{ptr}, _it{_list_to_element(cur)}
+    {
+        _next = _list_to_element(_it->list.next);
+    }
+
+    inline iterator(iterator&&) noexcept = default;
+
+    inline iterator(const iterator&) = delete;
+
+    inline auto operator=(const iterator&) -> iterator& = delete;
+
+    inline auto operator=(iterator &&) -> iterator& = delete;
+
+    auto operator*() -> T
+    {
+        return T{_ptr, _it};
+    }
+
+    inline auto operator!=(const iterator& rhs) -> bool
+    {
+        return (&_it->list != &rhs._it->list);
+    }
+
+    inline auto operator++() -> iterator&
+    {
+        _it = _next;
+        _next = _list_to_element(_next->list.next);
+        return *this;
+    }
+
+    inline ~iterator() = default;
+};
+
+class locked_context {
+  private:
+    static std::mutex inuse;
+
+  public:
+    inline locked_context()
+    {
+        inuse.lock();
+    }
+
+    inline locked_context(locked_context&&) noexcept = default;
+
+    inline locked_context(const locked_context&) = delete;
+
+    inline auto operator=(const locked_context&) -> locked_context& = delete;
+
+    inline auto operator=(locked_context &&) -> locked_context& = delete;
+
+    // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
+    inline auto get() -> uci_context*  // is member to enforce inuse
+    {
+        static auto free_ctx = [](uci_context* ctx) { uci_free_context(ctx); };
+        static std::unique_ptr<uci_context, decltype(free_ctx)> lazy_ctx{uci_alloc_context(),
+                                                                         free_ctx};
+
+        if (!lazy_ctx) {  // it could be available on a later call:
+            lazy_ctx.reset(uci_alloc_context());
+            if (!lazy_ctx) {
+                throw std::runtime_error("uci error: cannot allocate context");
+            }
+        }
+
+        return lazy_ctx.get();
+    }
+
+    inline ~locked_context()
+    {
+        inuse.unlock();
+    }
+};
+
+template <class T>
+class element {
+  private:
+    uci_list* _begin = nullptr;
+
+    uci_list* _end = nullptr;
+
+    uci_ptr _ptr{};
+
+  protected:
+    [[nodiscard]] inline auto ptr() -> uci_ptr&
+    {
+        return _ptr;
+    }
+
+    [[nodiscard]] inline auto ptr() const -> const uci_ptr&
+    {
+        return _ptr;
+    }
+
+    void init_begin_end(uci_list* begin, uci_list* end)
+    {
+        _begin = begin;
+        _end = end;
+    }
+
+    inline explicit element(const uci_ptr& pre, uci_element* last) : _ptr{pre}
+    {
+        _ptr.last = last;
+    }
+
+    inline explicit element() = default;
+
+  public:
+    inline element(element&&) noexcept = default;
+
+    inline element(const element&) = delete;
+
+    inline auto operator=(const element&) -> element& = delete;
+
+    inline auto operator=(element &&) -> element& = delete;
+
+    auto operator[](std::string_view key) const -> T;
+
+    [[nodiscard]] inline auto name() const -> std::string
+    {
+        return _ptr.last->name;
+    }
+
+    void rename(const char* value) const;
+
+    void commit() const;
+
+    [[nodiscard]] inline auto begin() const -> iterator<T>
+    {
+        return iterator<T>{_ptr, _begin};
+    }
+
+    [[nodiscard]] inline auto end() const -> iterator<T>
+    {
+        return iterator<T>{_ptr, _end};
+    }
+
+    inline ~element() = default;
+};
+
+class section;
+
+class option;
+
+class item;
+
+class package : public element<section> {
+  public:
+    inline package(const uci_ptr& pre, uci_element* last) : element{pre, last}
+    {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast)
+        ptr().p = uci_to_package(ptr().last);  // macro casting pointer-offset.
+        ptr().package = ptr().last->name;
+
+        auto* end = &ptr().p->sections;
+        auto* begin = end->next;
+        init_begin_end(begin, end);
+    }
+
+    explicit package(const char* name);
+
+    auto set(const char* key, const char* type) const -> section;
+};
+
+class section : public element<option> {
+  public:
+    inline section(const uci_ptr& pre, uci_element* last) : element{pre, last}
+    {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast)
+        ptr().s = uci_to_section(ptr().last);  // macro casting pointer-offset.
+        ptr().section = ptr().last->name;
+
+        auto* end = &ptr().s->options;
+        auto* begin = end->next;
+        init_begin_end(begin, end);
+    }
+
+    auto set(const char* key, const char* value) const -> option;
+
+    void del();
+
+    [[nodiscard]] inline auto anonymous() const -> bool
+    {
+        return ptr().s->anonymous;
+    }
+
+    [[nodiscard]] inline auto type() const -> std::string
+    {
+        return ptr().s->type;
+    }
+};
+
+class option : public element<item> {
+  public:
+    inline option(const uci_ptr& pre, uci_element* last) : element{pre, last}
+    {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast)
+        ptr().o = uci_to_option(ptr().last);  // macro casting pointer-offset.
+        ptr().option = ptr().last->name;
+
+        if (ptr().o->type == UCI_TYPE_LIST) {  // use union ptr().o->v as list:
+            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
+            auto* end = &ptr().o->v.list;
+            auto* begin = end->next;
+            init_begin_end(begin, end);
+        }
+        else {
+            auto* begin = &ptr().last->list;
+            auto* end = begin->next;
+            init_begin_end(begin, end);
+        }
+    }
+
+    void del();
+
+    [[nodiscard]] inline auto type() const -> std::string
+    {
+        return (ptr().o->type == UCI_TYPE_LIST ? "list" : "option");
+    }
+};
+
+class item : public element<item> {
+  public:
+    inline item(const uci_ptr& pre, uci_element* last) : element{pre, last}
+    {
+        ptr().value = ptr().last->name;
+    }
+
+    [[nodiscard]] inline auto type() const -> std::string
+    {
+        return (ptr().o->type == UCI_TYPE_LIST ? "list" : "option");
+    }
+
+    [[nodiscard]] inline auto name() const -> std::string
+    {
+        return (ptr().last->type == UCI_TYPE_ITEM
+                    ? ptr().last->name
+                    :
+                    // else: use union ptr().o->v as string:
+                    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
+                    ptr().o->v.string);
+    }
+
+    inline explicit operator bool() const
+    {
+        const auto x = std::string_view{name()};
+
+        if (x == "0" || x == "off" || x == "false" || x == "no" || x == "disabled") {
+            return false;
+        }
+
+        if (x == "1" || x == "on" || x == "true" || x == "yes" || x == "enabled") {
+            return true;
+        }
+
+        auto errmsg = std::string{"uci_error: item is not bool "} + name();
+        throw std::runtime_error(errmsg);
+    }
+
+    void rename(const char* value) const;
+};
+
+// ------------------------- implementation: ----------------------------------
+
+std::mutex locked_context::inuse{};
+
+inline auto uci_error(uci_context* ctx, const char* prefix = nullptr) -> std::runtime_error
+{
+    char* errmsg = nullptr;
+    uci_get_errorstr(ctx, &errmsg, prefix);
+
+    std::unique_ptr<char, decltype(&std::free)> auto_free{errmsg, std::free};
+    return std::runtime_error{errmsg};
+}
+
+template <class T>
+auto element<T>::operator[](std::string_view key) const -> T
+{
+    for (auto elmt : *this) {
+        if (elmt.name() == key) {
+            return elmt;
+        }
+    }
+
+    auto errmsg = std::string{"uci error: cannot find "}.append(key);
+    throw uci_error(locked_context{}.get(), errmsg.c_str());
+}
+
+template <class T>
+void element<T>::rename(const char* value) const
+{
+    if (value == name()) {
+        return;
+    }
+
+    auto ctx = locked_context{};
+    auto tmp_ptr = uci_ptr{_ptr};
+    tmp_ptr.value = value;
+    if (uci_rename(ctx.get(), &tmp_ptr) != 0) {
+        auto errmsg = std::string{"uci error: cannot rename "}.append(name());
+        throw uci_error(ctx.get(), errmsg.c_str());
+    }
+}
+
+template <class T>
+void element<T>::commit() const
+{
+    auto ctx = locked_context{};
+    // TODO(pst) use when possible:
+    // if (uci_commit(ctx.get(), &_ptr.p, true) != 0) {
+    //    auto errmsg = std::string{"uci error: cannot commit "} + _ptr.package;
+    //    throw uci_error(ctx.get(), errmsg.c_str());
+    // }
+    auto err = uci_save(ctx.get(), _ptr.p);
+    if (err == 0) {
+        uci_package* tmp_pkg = nullptr;
+        uci_context* tmp_ctx = uci_alloc_context();
+        err = (tmp_ctx == nullptr ? 1 : 0);
+        if (err == 0) {
+            err = uci_load(tmp_ctx, _ptr.package, &tmp_pkg);
+        }
+        if (err == 0) {
+            err = uci_commit(tmp_ctx, &tmp_pkg, false);
+        }
+        if (err == 0) {
+            err = uci_unload(tmp_ctx, tmp_pkg);
+        }
+        if (tmp_ctx != nullptr) {
+            uci_free_context(tmp_ctx);
+        }
+    }
+
+    if (err != 0) {
+        auto errmsg = std::string{"uci error: cannot commit "} + _ptr.package;
+        throw uci_error(ctx.get(), errmsg.c_str());
+    }
+}
+
+package::package(const char* name)
+{
+    auto ctx = locked_context{};
+
+    auto* pkg = uci_lookup_package(ctx.get(), name);
+    if (pkg == nullptr) {
+        if (uci_load(ctx.get(), name, &pkg) != 0) {
+            auto errmsg = std::string{"uci error: cannot load package "} + name;
+            throw uci_error(ctx.get(), errmsg.c_str());
+        }
+    }
+
+    ptr().package = name;
+    ptr().p = pkg;
+    ptr().last = &pkg->e;
+
+    auto* end = &ptr().p->sections;
+    auto* begin = end->next;
+    init_begin_end(begin, end);
+}
+
+auto package::set(const char* key, const char* type) const -> section
+{
+    auto ctx = locked_context{};
+
+    auto tmp_ptr = uci_ptr{ptr()};
+    tmp_ptr.section = key;
+    tmp_ptr.value = type;
+    if (uci_set(ctx.get(), &tmp_ptr) != 0) {
+        auto errmsg = std::string{"uci error: cannot set section "} + type + "'" + key +
+                      "' in package " + name();
+        throw uci_error(ctx.get(), errmsg.c_str());
+    }
+
+    return section{ptr(), tmp_ptr.last};
+}
+
+auto section::set(const char* key, const char* value) const -> option
+{
+    auto ctx = locked_context{};
+
+    auto tmp_ptr = uci_ptr{ptr()};
+    tmp_ptr.option = key;
+    tmp_ptr.value = value;
+    if (uci_set(ctx.get(), &tmp_ptr) != 0) {
+        auto errmsg = std::string{"uci error: cannot set option "} + key + "'" + value +
+                      "' in package " + name();
+        throw uci_error(ctx.get(), errmsg.c_str());
+    }
+
+    return option{ptr(), tmp_ptr.last};
+}
+
+void section::del()
+{
+    auto ctx = locked_context{};
+    if (uci_delete(ctx.get(), &ptr()) != 0) {
+        auto errmsg = std::string{"uci error: cannot delete section "} + name();
+        throw uci_error(ctx.get(), errmsg.c_str());
+    }
+}
+
+void option::del()
+{
+    auto ctx = locked_context{};
+    if (uci_delete(ctx.get(), &ptr()) != 0) {
+        auto errmsg = std::string{"uci error: cannot delete option "} + name();
+        throw uci_error(ctx.get(), errmsg.c_str());
+    }
+}
+
+void item::rename(const char* value) const
+{
+    if (value == name()) {
+        return;
+    }
+
+    auto ctx = locked_context{};
+    auto tmp_ptr = uci_ptr{ptr()};
+
+    if (tmp_ptr.last->type != UCI_TYPE_ITEM) {
+        tmp_ptr.value = value;
+        if (uci_set(ctx.get(), &tmp_ptr) != 0) {
+            auto errmsg = std::string{"uci error: cannot rename item "} + name();
+            throw uci_error(ctx.get(), errmsg.c_str());
+        }
+        return;
+    }  // else:
+
+    tmp_ptr.value = tmp_ptr.last->name;
+    if (uci_del_list(ctx.get(), &tmp_ptr) != 0) {
+        auto errmsg = std::string{"uci error: cannot rename (del) "} + name();
+        throw uci_error(ctx.get(), errmsg.c_str());
+    }
+
+    tmp_ptr.value = value;
+    if (uci_add_list(ctx.get(), &tmp_ptr) != 0) {
+        auto errmsg = std::string{"uci error: cannot rename (add) "} + value;
+        throw uci_error(ctx.get(), errmsg.c_str());
+    }
+}
+
+}  // namespace uci
+
+#endif