Added new example filesystem
authorNikolaus Rath <Nikolaus@rath.org>
Sun, 5 May 2019 17:11:03 +0000 (13:11 -0400)
committerNikolaus Rath <Nikolaus@rath.org>
Thu, 9 May 2019 19:16:37 +0000 (14:16 -0500)
passthrough_hp puts emphasis and performance and correctness, rather
than simplicity.

.dir-locals.el
.travis.yml
ChangeLog.rst
example/cxxopts.hpp [new file with mode: 0644]
example/meson.build
example/passthrough_hp.cc [new file with mode: 0644]
meson.build
test/test_examples.py
test/test_syscalls.c
test/travis-build.sh

index a9d100fa157e99d2ef429edea2fb0220c1bfa759..628f512abd301f7f8e5b5d61e06e1eb879b5a8ca 100644 (file)
@@ -1,5 +1,27 @@
 ((python-mode . ((indent-tabs-mode . nil)))
  (autoconf-mode . ((indent-tabs-mode . t)))
+ (c++-mode . ((c-file-style . "k&r")
+              (indent-tabs-mode . nil)
+              (c-basic-offset . 4)
+              (c-file-offsets .
+                              ((block-close . 0)
+                               (brace-list-close . 0)
+                               (brace-list-entry . 0)
+                               (brace-list-intro . +)
+                               (case-label . 0)
+                               (class-close . 0)
+                               (defun-block-intro . +)
+                               (defun-close . 0)
+                               (defun-open . 0)
+                               (else-clause . 0)
+                               (inclass . +)
+                               (label . 0)
+                               (statement . 0)
+                               (statement-block-intro . +)
+                               (statement-case-intro . +)
+                               (statement-cont . +)
+                               (substatement . +)
+                               (topmost-intro . 0)))))
  (c-mode . ((c-file-style . "stroustrup")
            (indent-tabs-mode . t)
            (tab-width . 8)
index b9f8dd93735025e33fd1017fc8db4ba3588b3e02..bb1fbf926e361d9bddfa2b15f4fa696593a31bb2 100644 (file)
@@ -3,6 +3,7 @@ dist: xenial
 
 language:
     - c
+    - c++
 addons:
   apt:
     sources:
@@ -11,6 +12,7 @@ addons:
     - doxygen
     - valgrind
     - clang
+    - libstdc++-7-dev 
     - gcc
     - gcc-7
     - python3-pip
index 1137b57f0906639fb4782fc32894b770692681b4..2885775948843b0861b8d2c5b55706591e2a2a9c 100644 (file)
@@ -1,3 +1,10 @@
+Unreleased Changes
+==================
+
+* Added a new example (passthrough_hp). The functionality is similar
+  to passthrough_ll, but the implementation focuses on performance and
+  correctness rather than simplicity.
+
 libfuse 3.5.0 (2019-04-16)
 ==========================
 
diff --git a/example/cxxopts.hpp b/example/cxxopts.hpp
new file mode 100644 (file)
index 0000000..6fd170d
--- /dev/null
@@ -0,0 +1,2077 @@
+/*
+
+Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+#ifndef CXXOPTS_HPP_INCLUDED
+#define CXXOPTS_HPP_INCLUDED
+
+#include <cstring>
+#include <cctype>
+#include <exception>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <regex>
+#include <sstream>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#ifdef __cpp_lib_optional
+#include <optional>
+#define CXXOPTS_HAS_OPTIONAL
+#endif
+
+#define CXXOPTS__VERSION_MAJOR 2
+#define CXXOPTS__VERSION_MINOR 2
+#define CXXOPTS__VERSION_PATCH 0
+
+namespace cxxopts
+{
+  static constexpr struct {
+    uint8_t major, minor, patch;
+  } version = {
+    CXXOPTS__VERSION_MAJOR,
+    CXXOPTS__VERSION_MINOR,
+    CXXOPTS__VERSION_PATCH
+  };
+}
+
+//when we ask cxxopts to use Unicode, help strings are processed using ICU,
+//which results in the correct lengths being computed for strings when they
+//are formatted for the help output
+//it is necessary to make sure that <unicode/unistr.h> can be found by the
+//compiler, and that icu-uc is linked in to the binary.
+
+#ifdef CXXOPTS_USE_UNICODE
+#include <unicode/unistr.h>
+
+namespace cxxopts
+{
+  typedef icu::UnicodeString String;
+
+  inline
+  String
+  toLocalString(std::string s)
+  {
+    return icu::UnicodeString::fromUTF8(std::move(s));
+  }
+
+  class UnicodeStringIterator : public
+    std::iterator<std::forward_iterator_tag, int32_t>
+  {
+    public:
+
+    UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos)
+    : s(string)
+    , i(pos)
+    {
+    }
+
+    value_type
+    operator*() const
+    {
+      return s->char32At(i);
+    }
+
+    bool
+    operator==(const UnicodeStringIterator& rhs) const
+    {
+      return s == rhs.s && i == rhs.i;
+    }
+
+    bool
+    operator!=(const UnicodeStringIterator& rhs) const
+    {
+      return !(*this == rhs);
+    }
+
+    UnicodeStringIterator&
+    operator++()
+    {
+      ++i;
+      return *this;
+    }
+
+    UnicodeStringIterator
+    operator+(int32_t v)
+    {
+      return UnicodeStringIterator(s, i + v);
+    }
+
+    private:
+    const icu::UnicodeString* s;
+    int32_t i;
+  };
+
+  inline
+  String&
+  stringAppend(String&s, String a)
+  {
+    return s.append(std::move(a));
+  }
+
+  inline
+  String&
+  stringAppend(String& s, int n, UChar32 c)
+  {
+    for (int i = 0; i != n; ++i)
+    {
+      s.append(c);
+    }
+
+    return s;
+  }
+
+  template <typename Iterator>
+  String&
+  stringAppend(String& s, Iterator begin, Iterator end)
+  {
+    while (begin != end)
+    {
+      s.append(*begin);
+      ++begin;
+    }
+
+    return s;
+  }
+
+  inline
+  size_t
+  stringLength(const String& s)
+  {
+    return s.length();
+  }
+
+  inline
+  std::string
+  toUTF8String(const String& s)
+  {
+    std::string result;
+    s.toUTF8String(result);
+
+    return result;
+  }
+
+  inline
+  bool
+  empty(const String& s)
+  {
+    return s.isEmpty();
+  }
+}
+
+namespace std
+{
+  inline
+  cxxopts::UnicodeStringIterator
+  begin(const icu::UnicodeString& s)
+  {
+    return cxxopts::UnicodeStringIterator(&s, 0);
+  }
+
+  inline
+  cxxopts::UnicodeStringIterator
+  end(const icu::UnicodeString& s)
+  {
+    return cxxopts::UnicodeStringIterator(&s, s.length());
+  }
+}
+
+//ifdef CXXOPTS_USE_UNICODE
+#else
+
+namespace cxxopts
+{
+  typedef std::string String;
+
+  template <typename T>
+  T
+  toLocalString(T&& t)
+  {
+    return std::forward<T>(t);
+  }
+
+  inline
+  size_t
+  stringLength(const String& s)
+  {
+    return s.length();
+  }
+
+  inline
+  String&
+  stringAppend(String&s, String a)
+  {
+    return s.append(std::move(a));
+  }
+
+  inline
+  String&
+  stringAppend(String& s, size_t n, char c)
+  {
+    return s.append(n, c);
+  }
+
+  template <typename Iterator>
+  String&
+  stringAppend(String& s, Iterator begin, Iterator end)
+  {
+    return s.append(begin, end);
+  }
+
+  template <typename T>
+  std::string
+  toUTF8String(T&& t)
+  {
+    return std::forward<T>(t);
+  }
+
+  inline
+  bool
+  empty(const std::string& s)
+  {
+    return s.empty();
+  }
+}
+
+//ifdef CXXOPTS_USE_UNICODE
+#endif
+
+namespace cxxopts
+{
+  namespace
+  {
+#ifdef _WIN32
+    const std::string LQUOTE("\'");
+    const std::string RQUOTE("\'");
+#else
+    const std::string LQUOTE("‘");
+    const std::string RQUOTE("’");
+#endif
+  }
+
+  class Value : public std::enable_shared_from_this<Value>
+  {
+    public:
+
+    virtual ~Value() = default;
+
+    virtual
+    std::shared_ptr<Value>
+    clone() const = 0;
+
+    virtual void
+    parse(const std::string& text) const = 0;
+
+    virtual void
+    parse() const = 0;
+
+    virtual bool
+    has_default() const = 0;
+
+    virtual bool
+    is_container() const = 0;
+
+    virtual bool
+    has_implicit() const = 0;
+
+    virtual std::string
+    get_default_value() const = 0;
+
+    virtual std::string
+    get_implicit_value() const = 0;
+
+    virtual std::shared_ptr<Value>
+    default_value(const std::string& value) = 0;
+
+    virtual std::shared_ptr<Value>
+    implicit_value(const std::string& value) = 0;
+
+    virtual bool
+    is_boolean() const = 0;
+  };
+
+  class OptionException : public std::exception
+  {
+    public:
+    OptionException(const std::string& message)
+    : m_message(message)
+    {
+    }
+
+    virtual const char*
+    what() const noexcept
+    {
+      return m_message.c_str();
+    }
+
+    private:
+    std::string m_message;
+  };
+
+  class OptionSpecException : public OptionException
+  {
+    public:
+
+    OptionSpecException(const std::string& message)
+    : OptionException(message)
+    {
+    }
+  };
+
+  class OptionParseException : public OptionException
+  {
+    public:
+    OptionParseException(const std::string& message)
+    : OptionException(message)
+    {
+    }
+  };
+
+  class option_exists_error : public OptionSpecException
+  {
+    public:
+    option_exists_error(const std::string& option)
+    : OptionSpecException(u8"Option " + LQUOTE + option + RQUOTE + u8" already exists")
+    {
+    }
+  };
+
+  class invalid_option_format_error : public OptionSpecException
+  {
+    public:
+    invalid_option_format_error(const std::string& format)
+    : OptionSpecException(u8"Invalid option format " + LQUOTE + format + RQUOTE)
+    {
+    }
+  };
+
+  class option_syntax_exception : public OptionParseException {
+    public:
+    option_syntax_exception(const std::string& text)
+    : OptionParseException(u8"Argument " + LQUOTE + text + RQUOTE +
+        u8" starts with a - but has incorrect syntax")
+    {
+    }
+  };
+
+  class option_not_exists_exception : public OptionParseException
+  {
+    public:
+    option_not_exists_exception(const std::string& option)
+    : OptionParseException(u8"Option " + LQUOTE + option + RQUOTE + u8" does not exist")
+    {
+    }
+  };
+
+  class missing_argument_exception : public OptionParseException
+  {
+    public:
+    missing_argument_exception(const std::string& option)
+    : OptionParseException(
+        u8"Option " + LQUOTE + option + RQUOTE + u8" is missing an argument"
+      )
+    {
+    }
+  };
+
+  class option_requires_argument_exception : public OptionParseException
+  {
+    public:
+    option_requires_argument_exception(const std::string& option)
+    : OptionParseException(
+        u8"Option " + LQUOTE + option + RQUOTE + u8" requires an argument"
+      )
+    {
+    }
+  };
+
+  class option_not_has_argument_exception : public OptionParseException
+  {
+    public:
+    option_not_has_argument_exception
+    (
+      const std::string& option,
+      const std::string& arg
+    )
+    : OptionParseException(
+        u8"Option " + LQUOTE + option + RQUOTE +
+        u8" does not take an argument, but argument " +
+        LQUOTE + arg + RQUOTE + " given"
+      )
+    {
+    }
+  };
+
+  class option_not_present_exception : public OptionParseException
+  {
+    public:
+    option_not_present_exception(const std::string& option)
+    : OptionParseException(u8"Option " + LQUOTE + option + RQUOTE + u8" not present")
+    {
+    }
+  };
+
+  class argument_incorrect_type : public OptionParseException
+  {
+    public:
+    argument_incorrect_type
+    (
+      const std::string& arg
+    )
+    : OptionParseException(
+        u8"Argument " + LQUOTE + arg + RQUOTE + u8" failed to parse"
+      )
+    {
+    }
+  };
+
+  class option_required_exception : public OptionParseException
+  {
+    public:
+    option_required_exception(const std::string& option)
+    : OptionParseException(
+        u8"Option " + LQUOTE + option + RQUOTE + u8" is required but not present"
+      )
+    {
+    }
+  };
+
+  namespace values
+  {
+    namespace
+    {
+      std::basic_regex<char> integer_pattern
+        ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)");
+      std::basic_regex<char> truthy_pattern
+        ("(t|T)(rue)?");
+      std::basic_regex<char> falsy_pattern
+        ("((f|F)(alse)?)?");
+    }
+
+    namespace detail
+    {
+      template <typename T, bool B>
+      struct SignedCheck;
+
+      template <typename T>
+      struct SignedCheck<T, true>
+      {
+        template <typename U>
+        void
+        operator()(bool negative, U u, const std::string& text)
+        {
+          if (negative)
+          {
+            if (u > static_cast<U>(-std::numeric_limits<T>::min()))
+            {
+              throw argument_incorrect_type(text);
+            }
+          }
+          else
+          {
+            if (u > static_cast<U>(std::numeric_limits<T>::max()))
+            {
+              throw argument_incorrect_type(text);
+            }
+          }
+        }
+      };
+
+      template <typename T>
+      struct SignedCheck<T, false>
+      {
+        template <typename U>
+        void
+        operator()(bool, U, const std::string&) {}
+      };
+
+      template <typename T, typename U>
+      void
+      check_signed_range(bool negative, U value, const std::string& text)
+      {
+        SignedCheck<T, std::numeric_limits<T>::is_signed>()(negative, value, text);
+      }
+    }
+
+    template <typename R, typename T>
+    R
+    checked_negate(T&& t, const std::string&, std::true_type)
+    {
+      // if we got to here, then `t` is a positive number that fits into
+      // `R`. So to avoid MSVC C4146, we first cast it to `R`.
+      // See https://github.com/jarro2783/cxxopts/issues/62 for more details.
+      return -static_cast<R>(t);
+    }
+
+    template <typename R, typename T>
+    T
+    checked_negate(T&&, const std::string& text, std::false_type)
+    {
+      throw argument_incorrect_type(text);
+    }
+
+    template <typename T>
+    void
+    integer_parser(const std::string& text, T& value)
+    {
+      std::smatch match;
+      std::regex_match(text, match, integer_pattern);
+
+      if (match.length() == 0)
+      {
+        throw argument_incorrect_type(text);
+      }
+
+      if (match.length(4) > 0)
+      {
+        value = 0;
+        return;
+      }
+
+      using US = typename std::make_unsigned<T>::type;
+
+      constexpr auto umax = std::numeric_limits<US>::max();
+      constexpr bool is_signed = std::numeric_limits<T>::is_signed;
+      const bool negative = match.length(1) > 0;
+      const uint8_t base = match.length(2) > 0 ? 16 : 10;
+
+      auto value_match = match[3];
+
+      US result = 0;
+
+      for (auto iter = value_match.first; iter != value_match.second; ++iter)
+      {
+        US digit = 0;
+
+        if (*iter >= '0' && *iter <= '9')
+        {
+          digit = *iter - '0';
+        }
+        else if (base == 16 && *iter >= 'a' && *iter <= 'f')
+        {
+          digit = *iter - 'a' + 10;
+        }
+        else if (base == 16 && *iter >= 'A' && *iter <= 'F')
+        {
+          digit = *iter - 'A' + 10;
+        }
+        else
+        {
+          throw argument_incorrect_type(text);
+        }
+
+        if (umax - digit < result * base)
+        {
+          throw argument_incorrect_type(text);
+        }
+
+        result = result * base + digit;
+      }
+
+      detail::check_signed_range<T>(negative, result, text);
+
+      if (negative)
+      {
+        value = checked_negate<T>(result,
+          text,
+          std::integral_constant<bool, is_signed>());
+      }
+      else
+      {
+        value = result;
+      }
+    }
+
+    template <typename T>
+    void stringstream_parser(const std::string& text, T& value)
+    {
+      std::stringstream in(text);
+      in >> value;
+      if (!in) {
+        throw argument_incorrect_type(text);
+      }
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, uint8_t& value)
+    {
+      integer_parser(text, value);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, int8_t& value)
+    {
+      integer_parser(text, value);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, uint16_t& value)
+    {
+      integer_parser(text, value);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, int16_t& value)
+    {
+      integer_parser(text, value);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, uint32_t& value)
+    {
+      integer_parser(text, value);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, int32_t& value)
+    {
+      integer_parser(text, value);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, uint64_t& value)
+    {
+      integer_parser(text, value);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, int64_t& value)
+    {
+      integer_parser(text, value);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, bool& value)
+    {
+      std::smatch result;
+      std::regex_match(text, result, truthy_pattern);
+
+      if (!result.empty())
+      {
+        value = true;
+        return;
+      }
+
+      std::regex_match(text, result, falsy_pattern);
+      if (!result.empty())
+      {
+        value = false;
+        return;
+      }
+
+      throw argument_incorrect_type(text);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, std::string& value)
+    {
+      value = text;
+    }
+
+    // The fallback parser. It uses the stringstream parser to parse all types
+    // that have not been overloaded explicitly.  It has to be placed in the
+    // source code before all other more specialized templates.
+    template <typename T>
+    void
+    parse_value(const std::string& text, T& value) {
+      stringstream_parser(text, value);
+    }
+
+    template <typename T>
+    void
+    parse_value(const std::string& text, std::vector<T>& value)
+    {
+      T v;
+      parse_value(text, v);
+      value.push_back(v);
+    }
+
+#ifdef CXXOPTS_HAS_OPTIONAL
+    template <typename T>
+    void
+    parse_value(const std::string& text, std::optional<T>& value)
+    {
+      T result;
+      parse_value(text, result);
+      value = std::move(result);
+    }
+#endif
+
+    template <typename T>
+    struct type_is_container
+    {
+      static constexpr bool value = false;
+    };
+
+    template <typename T>
+    struct type_is_container<std::vector<T>>
+    {
+      static constexpr bool value = true;
+    };
+
+    template <typename T>
+    class abstract_value : public Value
+    {
+      using Self = abstract_value<T>;
+
+      public:
+      abstract_value()
+      : m_result(std::make_shared<T>())
+      , m_store(m_result.get())
+      {
+      }
+
+      abstract_value(T* t)
+      : m_store(t)
+      {
+      }
+
+      virtual ~abstract_value() = default;
+
+      abstract_value(const abstract_value& rhs)
+      {
+        if (rhs.m_result)
+        {
+          m_result = std::make_shared<T>();
+          m_store = m_result.get();
+        }
+        else
+        {
+          m_store = rhs.m_store;
+        }
+
+        m_default = rhs.m_default;
+        m_implicit = rhs.m_implicit;
+        m_default_value = rhs.m_default_value;
+        m_implicit_value = rhs.m_implicit_value;
+      }
+
+      void
+      parse(const std::string& text) const
+      {
+        parse_value(text, *m_store);
+      }
+
+      bool
+      is_container() const
+      {
+        return type_is_container<T>::value;
+      }
+
+      void
+      parse() const
+      {
+        parse_value(m_default_value, *m_store);
+      }
+
+      bool
+      has_default() const
+      {
+        return m_default;
+      }
+
+      bool
+      has_implicit() const
+      {
+        return m_implicit;
+      }
+
+      std::shared_ptr<Value>
+      default_value(const std::string& value)
+      {
+        m_default = true;
+        m_default_value = value;
+        return shared_from_this();
+      }
+
+      std::shared_ptr<Value>
+      implicit_value(const std::string& value)
+      {
+        m_implicit = true;
+        m_implicit_value = value;
+        return shared_from_this();
+      }
+
+      std::string
+      get_default_value() const
+      {
+        return m_default_value;
+      }
+
+      std::string
+      get_implicit_value() const
+      {
+        return m_implicit_value;
+      }
+
+      bool
+      is_boolean() const
+      {
+        return std::is_same<T, bool>::value;
+      }
+
+      const T&
+      get() const
+      {
+        if (m_store == nullptr)
+        {
+          return *m_result;
+        }
+        else
+        {
+          return *m_store;
+        }
+      }
+
+      protected:
+      std::shared_ptr<T> m_result;
+      T* m_store;
+
+      bool m_default = false;
+      bool m_implicit = false;
+
+      std::string m_default_value;
+      std::string m_implicit_value;
+    };
+
+    template <typename T>
+    class standard_value : public abstract_value<T>
+    {
+      public:
+      using abstract_value<T>::abstract_value;
+
+      std::shared_ptr<Value>
+      clone() const
+      {
+        return std::make_shared<standard_value<T>>(*this);
+      }
+    };
+
+    template <>
+    class standard_value<bool> : public abstract_value<bool>
+    {
+      public:
+      ~standard_value() = default;
+
+      standard_value()
+      {
+        set_default_and_implicit();
+      }
+
+      standard_value(bool* b)
+      : abstract_value(b)
+      {
+        set_default_and_implicit();
+      }
+
+      std::shared_ptr<Value>
+      clone() const
+      {
+        return std::make_shared<standard_value<bool>>(*this);
+      }
+
+      private:
+
+      void
+      set_default_and_implicit()
+      {
+        m_default = true;
+        m_default_value = "false";
+        m_implicit = true;
+        m_implicit_value = "true";
+      }
+    };
+  }
+
+  template <typename T>
+  std::shared_ptr<Value>
+  value()
+  {
+    return std::make_shared<values::standard_value<T>>();
+  }
+
+  template <typename T>
+  std::shared_ptr<Value>
+  value(T& t)
+  {
+    return std::make_shared<values::standard_value<T>>(&t);
+  }
+
+  class OptionAdder;
+
+  class OptionDetails
+  {
+    public:
+    OptionDetails
+    (
+      const std::string& short_,
+      const std::string& long_,
+      const String& desc,
+      std::shared_ptr<const Value> val
+    )
+    : m_short(short_)
+    , m_long(long_)
+    , m_desc(desc)
+    , m_value(val)
+    , m_count(0)
+    {
+    }
+
+    OptionDetails(const OptionDetails& rhs)
+    : m_desc(rhs.m_desc)
+    , m_count(rhs.m_count)
+    {
+      m_value = rhs.m_value->clone();
+    }
+
+    OptionDetails(OptionDetails&& rhs) = default;
+
+    const String&
+    description() const
+    {
+      return m_desc;
+    }
+
+    const Value& value() const {
+        return *m_value;
+    }
+
+    std::shared_ptr<Value>
+    make_storage() const
+    {
+      return m_value->clone();
+    }
+
+    const std::string&
+    short_name() const
+    {
+      return m_short;
+    }
+
+    const std::string&
+    long_name() const
+    {
+      return m_long;
+    }
+
+    private:
+    std::string m_short;
+    std::string m_long;
+    String m_desc;
+    std::shared_ptr<const Value> m_value;
+    int m_count;
+  };
+
+  struct HelpOptionDetails
+  {
+    std::string s;
+    std::string l;
+    String desc;
+    bool has_default;
+    std::string default_value;
+    bool has_implicit;
+    std::string implicit_value;
+    std::string arg_help;
+    bool is_container;
+    bool is_boolean;
+  };
+
+  struct HelpGroupDetails
+  {
+    std::string name;
+    std::string description;
+    std::vector<HelpOptionDetails> options;
+  };
+
+  class OptionValue
+  {
+    public:
+    void
+    parse
+    (
+      std::shared_ptr<const OptionDetails> details,
+      const std::string& text
+    )
+    {
+      ensure_value(details);
+      ++m_count;
+      m_value->parse(text);
+    }
+
+    void
+    parse_default(std::shared_ptr<const OptionDetails> details)
+    {
+      ensure_value(details);
+      m_value->parse();
+    }
+
+    size_t
+    count() const
+    {
+      return m_count;
+    }
+
+    template <typename T>
+    const T&
+    as() const
+    {
+#ifdef CXXOPTS_NO_RTTI
+      return static_cast<const values::standard_value<T>&>(*m_value).get();
+#else
+      return dynamic_cast<const values::standard_value<T>&>(*m_value).get();
+#endif
+    }
+
+    private:
+    void
+    ensure_value(std::shared_ptr<const OptionDetails> details)
+    {
+      if (m_value == nullptr)
+      {
+        m_value = details->make_storage();
+      }
+    }
+
+    std::shared_ptr<Value> m_value;
+    size_t m_count = 0;
+  };
+
+  class KeyValue
+  {
+    public:
+    KeyValue(std::string key_, std::string value_)
+    : m_key(std::move(key_))
+    , m_value(std::move(value_))
+    {
+    }
+
+    const
+    std::string&
+    key() const
+    {
+      return m_key;
+    }
+
+    const std::string
+    value() const
+    {
+      return m_value;
+    }
+
+    template <typename T>
+    T
+    as() const
+    {
+      T result;
+      values::parse_value(m_value, result);
+      return result;
+    }
+
+    private:
+    std::string m_key;
+    std::string m_value;
+  };
+
+  class ParseResult
+  {
+    public:
+
+    ParseResult(
+      const std::shared_ptr<
+        std::unordered_map<std::string, std::shared_ptr<OptionDetails>>
+      >,
+      std::vector<std::string>,
+      bool allow_unrecognised,
+      int&, char**&);
+
+    size_t
+    count(const std::string& o) const
+    {
+      auto iter = m_options->find(o);
+      if (iter == m_options->end())
+      {
+        return 0;
+      }
+
+      auto riter = m_results.find(iter->second);
+
+      return riter->second.count();
+    }
+
+    const OptionValue&
+    operator[](const std::string& option) const
+    {
+      auto iter = m_options->find(option);
+
+      if (iter == m_options->end())
+      {
+        throw option_not_present_exception(option);
+      }
+
+      auto riter = m_results.find(iter->second);
+
+      return riter->second;
+    }
+
+    const std::vector<KeyValue>&
+    arguments() const
+    {
+      return m_sequential;
+    }
+
+    private:
+
+    void
+    parse(int& argc, char**& argv);
+
+    void
+    add_to_option(const std::string& option, const std::string& arg);
+
+    bool
+    consume_positional(std::string a);
+
+    void
+    parse_option
+    (
+      std::shared_ptr<OptionDetails> value,
+      const std::string& name,
+      const std::string& arg = ""
+    );
+
+    void
+    parse_default(std::shared_ptr<OptionDetails> details);
+
+    void
+    checked_parse_arg
+    (
+      int argc,
+      char* argv[],
+      int& current,
+      std::shared_ptr<OptionDetails> value,
+      const std::string& name
+    );
+
+    const std::shared_ptr<
+      std::unordered_map<std::string, std::shared_ptr<OptionDetails>>
+    > m_options;
+    std::vector<std::string> m_positional;
+    std::vector<std::string>::iterator m_next_positional;
+    std::unordered_set<std::string> m_positional_set;
+    std::unordered_map<std::shared_ptr<OptionDetails>, OptionValue> m_results;
+
+    bool m_allow_unrecognised;
+
+    std::vector<KeyValue> m_sequential;
+  };
+
+  class Options
+  {
+    typedef std::unordered_map<std::string, std::shared_ptr<OptionDetails>>
+      OptionMap;
+    public:
+
+    Options(std::string program, std::string help_string = "")
+    : m_program(std::move(program))
+    , m_help_string(toLocalString(std::move(help_string)))
+    , m_custom_help("[OPTION...]")
+    , m_positional_help("positional parameters")
+    , m_show_positional(false)
+    , m_allow_unrecognised(false)
+    , m_options(std::make_shared<OptionMap>())
+    , m_next_positional(m_positional.end())
+    {
+    }
+
+    Options&
+    positional_help(std::string help_text)
+    {
+      m_positional_help = std::move(help_text);
+      return *this;
+    }
+
+    Options&
+    custom_help(std::string help_text)
+    {
+      m_custom_help = std::move(help_text);
+      return *this;
+    }
+
+    Options&
+    show_positional_help()
+    {
+      m_show_positional = true;
+      return *this;
+    }
+
+    Options&
+    allow_unrecognised_options()
+    {
+      m_allow_unrecognised = true;
+      return *this;
+    }
+
+    ParseResult
+    parse(int& argc, char**& argv);
+
+    OptionAdder
+    add_options(std::string group = "");
+
+    void
+    add_option
+    (
+      const std::string& group,
+      const std::string& s,
+      const std::string& l,
+      std::string desc,
+      std::shared_ptr<const Value> value,
+      std::string arg_help
+    );
+
+    //parse positional arguments into the given option
+    void
+    parse_positional(std::string option);
+
+    void
+    parse_positional(std::vector<std::string> options);
+
+    void
+    parse_positional(std::initializer_list<std::string> options);
+
+    template <typename Iterator>
+    void
+    parse_positional(Iterator begin, Iterator end) {
+      parse_positional(std::vector<std::string>{begin, end});
+    }
+
+    std::string
+    help(const std::vector<std::string>& groups = {""}) const;
+
+    const std::vector<std::string>
+    groups() const;
+
+    const HelpGroupDetails&
+    group_help(const std::string& group) const;
+
+    private:
+
+    void
+    add_one_option
+    (
+      const std::string& option,
+      std::shared_ptr<OptionDetails> details
+    );
+
+    String
+    help_one_group(const std::string& group) const;
+
+    void
+    generate_group_help
+    (
+      String& result,
+      const std::vector<std::string>& groups
+    ) const;
+
+    void
+    generate_all_groups_help(String& result) const;
+
+    std::string m_program;
+    String m_help_string;
+    std::string m_custom_help;
+    std::string m_positional_help;
+    bool m_show_positional;
+    bool m_allow_unrecognised;
+
+    std::shared_ptr<OptionMap> m_options;
+    std::vector<std::string> m_positional;
+    std::vector<std::string>::iterator m_next_positional;
+    std::unordered_set<std::string> m_positional_set;
+
+    //mapping from groups to help options
+    std::map<std::string, HelpGroupDetails> m_help;
+  };
+
+  class OptionAdder
+  {
+    public:
+
+    OptionAdder(Options& options, std::string group)
+    : m_options(options), m_group(std::move(group))
+    {
+    }
+
+    OptionAdder&
+    operator()
+    (
+      const std::string& opts,
+      const std::string& desc,
+      std::shared_ptr<const Value> value
+        = ::cxxopts::value<bool>(),
+      std::string arg_help = ""
+    );
+
+    private:
+    Options& m_options;
+    std::string m_group;
+  };
+
+  namespace
+  {
+    constexpr int OPTION_LONGEST = 30;
+    constexpr int OPTION_DESC_GAP = 2;
+
+    std::basic_regex<char> option_matcher
+      ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)");
+
+    std::basic_regex<char> option_specifier
+      ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?");
+
+    String
+    format_option
+    (
+      const HelpOptionDetails& o
+    )
+    {
+      auto& s = o.s;
+      auto& l = o.l;
+
+      String result = "  ";
+
+      if (s.size() > 0)
+      {
+        result += "-" + toLocalString(s) + ",";
+      }
+      else
+      {
+        result += "   ";
+      }
+
+      if (l.size() > 0)
+      {
+        result += " --" + toLocalString(l);
+      }
+
+      auto arg = o.arg_help.size() > 0 ? toLocalString(o.arg_help) : "arg";
+
+      if (!o.is_boolean)
+      {
+        if (o.has_implicit)
+        {
+          result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]";
+        }
+        else
+        {
+          result += " " + arg;
+        }
+      }
+
+      return result;
+    }
+
+    String
+    format_description
+    (
+      const HelpOptionDetails& o,
+      size_t start,
+      size_t width
+    )
+    {
+      auto desc = o.desc;
+
+      if (o.has_default && (!o.is_boolean || o.default_value != "false"))
+      {
+        desc += toLocalString(" (default: " + o.default_value + ")");
+      }
+
+      String result;
+
+      auto current = std::begin(desc);
+      auto startLine = current;
+      auto lastSpace = current;
+
+      auto size = size_t{};
+
+      while (current != std::end(desc))
+      {
+        if (*current == ' ')
+        {
+          lastSpace = current;
+        }
+
+        if (*current == '\n')
+        {
+          startLine = current + 1;
+          lastSpace = startLine;
+        }
+        else if (size > width)
+        {
+          if (lastSpace == startLine)
+          {
+            stringAppend(result, startLine, current + 1);
+            stringAppend(result, "\n");
+            stringAppend(result, start, ' ');
+            startLine = current + 1;
+            lastSpace = startLine;
+          }
+          else
+          {
+            stringAppend(result, startLine, lastSpace);
+            stringAppend(result, "\n");
+            stringAppend(result, start, ' ');
+            startLine = lastSpace + 1;
+          }
+          size = 0;
+        }
+        else
+        {
+          ++size;
+        }
+
+        ++current;
+      }
+
+      //append whatever is left
+      stringAppend(result, startLine, current);
+
+      return result;
+    }
+  }
+
+inline
+ParseResult::ParseResult
+(
+  const std::shared_ptr<
+    std::unordered_map<std::string, std::shared_ptr<OptionDetails>>
+  > options,
+  std::vector<std::string> positional,
+  bool allow_unrecognised,
+  int& argc, char**& argv
+)
+: m_options(options)
+, m_positional(std::move(positional))
+, m_next_positional(m_positional.begin())
+, m_allow_unrecognised(allow_unrecognised)
+{
+  parse(argc, argv);
+}
+
+inline
+OptionAdder
+Options::add_options(std::string group)
+{
+  return OptionAdder(*this, std::move(group));
+}
+
+inline
+OptionAdder&
+OptionAdder::operator()
+(
+  const std::string& opts,
+  const std::string& desc,
+  std::shared_ptr<const Value> value,
+  std::string arg_help
+)
+{
+  std::match_results<const char*> result;
+  std::regex_match(opts.c_str(), result, option_specifier);
+
+  if (result.empty())
+  {
+    throw invalid_option_format_error(opts);
+  }
+
+  const auto& short_match = result[2];
+  const auto& long_match = result[3];
+
+  if (!short_match.length() && !long_match.length())
+  {
+    throw invalid_option_format_error(opts);
+  } else if (long_match.length() == 1 && short_match.length())
+  {
+    throw invalid_option_format_error(opts);
+  }
+
+  auto option_names = []
+  (
+    const std::sub_match<const char*>& short_,
+    const std::sub_match<const char*>& long_
+  )
+  {
+    if (long_.length() == 1)
+    {
+      return std::make_tuple(long_.str(), short_.str());
+    }
+    else
+    {
+      return std::make_tuple(short_.str(), long_.str());
+    }
+  }(short_match, long_match);
+
+  m_options.add_option
+  (
+    m_group,
+    std::get<0>(option_names),
+    std::get<1>(option_names),
+    desc,
+    value,
+    std::move(arg_help)
+  );
+
+  return *this;
+}
+
+inline
+void
+ParseResult::parse_default(std::shared_ptr<OptionDetails> details)
+{
+  m_results[details].parse_default(details);
+}
+
+inline
+void
+ParseResult::parse_option
+(
+  std::shared_ptr<OptionDetails> value,
+  const std::string& /*name*/,
+  const std::string& arg
+)
+{
+  auto& result = m_results[value];
+  result.parse(value, arg);
+
+  m_sequential.emplace_back(value->long_name(), arg);
+}
+
+inline
+void
+ParseResult::checked_parse_arg
+(
+  int argc,
+  char* argv[],
+  int& current,
+  std::shared_ptr<OptionDetails> value,
+  const std::string& name
+)
+{
+  if (current + 1 >= argc)
+  {
+    if (value->value().has_implicit())
+    {
+      parse_option(value, name, value->value().get_implicit_value());
+    }
+    else
+    {
+      throw missing_argument_exception(name);
+    }
+  }
+  else
+  {
+    if (value->value().has_implicit())
+    {
+      parse_option(value, name, value->value().get_implicit_value());
+    }
+    else
+    {
+      parse_option(value, name, argv[current + 1]);
+      ++current;
+    }
+  }
+}
+
+inline
+void
+ParseResult::add_to_option(const std::string& option, const std::string& arg)
+{
+  auto iter = m_options->find(option);
+
+  if (iter == m_options->end())
+  {
+    throw option_not_exists_exception(option);
+  }
+
+  parse_option(iter->second, option, arg);
+}
+
+inline
+bool
+ParseResult::consume_positional(std::string a)
+{
+  while (m_next_positional != m_positional.end())
+  {
+    auto iter = m_options->find(*m_next_positional);
+    if (iter != m_options->end())
+    {
+      auto& result = m_results[iter->second];
+      if (!iter->second->value().is_container())
+      {
+        if (result.count() == 0)
+        {
+          add_to_option(*m_next_positional, a);
+          ++m_next_positional;
+          return true;
+        }
+        else
+        {
+          ++m_next_positional;
+          continue;
+        }
+      }
+      else
+      {
+        add_to_option(*m_next_positional, a);
+        return true;
+      }
+    }
+    ++m_next_positional;
+  }
+
+  return false;
+}
+
+inline
+void
+Options::parse_positional(std::string option)
+{
+  parse_positional(std::vector<std::string>{std::move(option)});
+}
+
+inline
+void
+Options::parse_positional(std::vector<std::string> options)
+{
+  m_positional = std::move(options);
+  m_next_positional = m_positional.begin();
+
+  m_positional_set.insert(m_positional.begin(), m_positional.end());
+}
+
+inline
+void
+Options::parse_positional(std::initializer_list<std::string> options)
+{
+  parse_positional(std::vector<std::string>(std::move(options)));
+}
+
+inline
+ParseResult
+Options::parse(int& argc, char**& argv)
+{
+  ParseResult result(m_options, m_positional, m_allow_unrecognised, argc, argv);
+  return result;
+}
+
+inline
+void
+ParseResult::parse(int& argc, char**& argv)
+{
+  int current = 1;
+
+  int nextKeep = 1;
+
+  bool consume_remaining = false;
+
+  while (current != argc)
+  {
+    if (strcmp(argv[current], "--") == 0)
+    {
+      consume_remaining = true;
+      ++current;
+      break;
+    }
+
+    std::match_results<const char*> result;
+    std::regex_match(argv[current], result, option_matcher);
+
+    if (result.empty())
+    {
+      //not a flag
+
+      // but if it starts with a `-`, then it's an error
+      if (argv[current][0] == '-') {
+        throw option_syntax_exception(argv[current]);
+      }
+
+      //if true is returned here then it was consumed, otherwise it is
+      //ignored
+      if (consume_positional(argv[current]))
+      {
+      }
+      else
+      {
+        argv[nextKeep] = argv[current];
+        ++nextKeep;
+      }
+      //if we return from here then it was parsed successfully, so continue
+    }
+    else
+    {
+      //short or long option?
+      if (result[4].length() != 0)
+      {
+        const std::string& s = result[4];
+
+        for (std::size_t i = 0; i != s.size(); ++i)
+        {
+          std::string name(1, s[i]);
+          auto iter = m_options->find(name);
+
+          if (iter == m_options->end())
+          {
+            if (m_allow_unrecognised)
+            {
+              continue;
+            }
+            else
+            {
+              //error
+              throw option_not_exists_exception(name);
+            }
+          }
+
+          auto value = iter->second;
+
+          if (i + 1 == s.size())
+          {
+            //it must be the last argument
+            checked_parse_arg(argc, argv, current, value, name);
+          }
+          else if (value->value().has_implicit())
+          {
+            parse_option(value, name, value->value().get_implicit_value());
+          }
+          else
+          {
+            //error
+            throw option_requires_argument_exception(name);
+          }
+        }
+      }
+      else if (result[1].length() != 0)
+      {
+        const std::string& name = result[1];
+
+        auto iter = m_options->find(name);
+
+        if (iter == m_options->end())
+        {
+          if (m_allow_unrecognised)
+          {
+            // keep unrecognised options in argument list, skip to next argument
+            argv[nextKeep] = argv[current];
+            ++nextKeep;
+            ++current;
+            continue;
+          }
+          else
+          {
+            //error
+            throw option_not_exists_exception(name);
+          }
+        }
+
+        auto opt = iter->second;
+
+        //equals provided for long option?
+        if (result[2].length() != 0)
+        {
+          //parse the option given
+
+          parse_option(opt, name, result[3]);
+        }
+        else
+        {
+          //parse the next argument
+          checked_parse_arg(argc, argv, current, opt, name);
+        }
+      }
+
+    }
+
+    ++current;
+  }
+
+  for (auto& opt : *m_options)
+  {
+    auto& detail = opt.second;
+    auto& value = detail->value();
+
+    auto& store = m_results[detail];
+
+    if(!store.count() && value.has_default()){
+      parse_default(detail);
+    }
+  }
+
+  if (consume_remaining)
+  {
+    while (current < argc)
+    {
+      if (!consume_positional(argv[current])) {
+        break;
+      }
+      ++current;
+    }
+
+    //adjust argv for any that couldn't be swallowed
+    while (current != argc) {
+      argv[nextKeep] = argv[current];
+      ++nextKeep;
+      ++current;
+    }
+  }
+
+  argc = nextKeep;
+
+}
+
+inline
+void
+Options::add_option
+(
+  const std::string& group,
+  const std::string& s,
+  const std::string& l,
+  std::string desc,
+  std::shared_ptr<const Value> value,
+  std::string arg_help
+)
+{
+  auto stringDesc = toLocalString(std::move(desc));
+  auto option = std::make_shared<OptionDetails>(s, l, stringDesc, value);
+
+  if (s.size() > 0)
+  {
+    add_one_option(s, option);
+  }
+
+  if (l.size() > 0)
+  {
+    add_one_option(l, option);
+  }
+
+  //add the help details
+  auto& options = m_help[group];
+
+  options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,
+      value->has_default(), value->get_default_value(),
+      value->has_implicit(), value->get_implicit_value(),
+      std::move(arg_help),
+      value->is_container(),
+      value->is_boolean()});
+}
+
+inline
+void
+Options::add_one_option
+(
+  const std::string& option,
+  std::shared_ptr<OptionDetails> details
+)
+{
+  auto in = m_options->emplace(option, details);
+
+  if (!in.second)
+  {
+    throw option_exists_error(option);
+  }
+}
+
+inline
+String
+Options::help_one_group(const std::string& g) const
+{
+  typedef std::vector<std::pair<String, String>> OptionHelp;
+
+  auto group = m_help.find(g);
+  if (group == m_help.end())
+  {
+    return "";
+  }
+
+  OptionHelp format;
+
+  size_t longest = 0;
+
+  String result;
+
+  if (!g.empty())
+  {
+    result += toLocalString(" " + g + " options:\n");
+  }
+
+  for (const auto& o : group->second.options)
+  {
+    if (o.is_container &&
+        m_positional_set.find(o.l) != m_positional_set.end() &&
+        !m_show_positional)
+    {
+      continue;
+    }
+
+    auto s = format_option(o);
+    longest = std::max(longest, stringLength(s));
+    format.push_back(std::make_pair(s, String()));
+  }
+
+  longest = std::min(longest, static_cast<size_t>(OPTION_LONGEST));
+
+  //widest allowed description
+  auto allowed = size_t{76} - longest - OPTION_DESC_GAP;
+
+  auto fiter = format.begin();
+  for (const auto& o : group->second.options)
+  {
+    if (o.is_container &&
+        m_positional_set.find(o.l) != m_positional_set.end() &&
+        !m_show_positional)
+    {
+      continue;
+    }
+
+    auto d = format_description(o, longest + OPTION_DESC_GAP, allowed);
+
+    result += fiter->first;
+    if (stringLength(fiter->first) > longest)
+    {
+      result += '\n';
+      result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' '));
+    }
+    else
+    {
+      result += toLocalString(std::string(longest + OPTION_DESC_GAP -
+        stringLength(fiter->first),
+        ' '));
+    }
+    result += d;
+    result += '\n';
+
+    ++fiter;
+  }
+
+  return result;
+}
+
+inline
+void
+Options::generate_group_help
+(
+  String& result,
+  const std::vector<std::string>& print_groups
+) const
+{
+  for (size_t i = 0; i != print_groups.size(); ++i)
+  {
+    const String& group_help_text = help_one_group(print_groups[i]);
+    if (empty(group_help_text))
+    {
+      continue;
+    }
+    result += group_help_text;
+    if (i < print_groups.size() - 1)
+    {
+      result += '\n';
+    }
+  }
+}
+
+inline
+void
+Options::generate_all_groups_help(String& result) const
+{
+  std::vector<std::string> all_groups;
+  all_groups.reserve(m_help.size());
+
+  for (auto& group : m_help)
+  {
+    all_groups.push_back(group.first);
+  }
+
+  generate_group_help(result, all_groups);
+}
+
+inline
+std::string
+Options::help(const std::vector<std::string>& help_groups) const
+{
+  String result = m_help_string + "\nUsage:\n  " +
+    toLocalString(m_program) + " " + toLocalString(m_custom_help);
+
+  if (m_positional.size() > 0 && m_positional_help.size() > 0) {
+    result += " " + toLocalString(m_positional_help);
+  }
+
+  result += "\n\n";
+
+  if (help_groups.size() == 0)
+  {
+    generate_all_groups_help(result);
+  }
+  else
+  {
+    generate_group_help(result, help_groups);
+  }
+
+  return toUTF8String(result);
+}
+
+inline
+const std::vector<std::string>
+Options::groups() const
+{
+  std::vector<std::string> g;
+
+  std::transform(
+    m_help.begin(),
+    m_help.end(),
+    std::back_inserter(g),
+    [] (const std::map<std::string, HelpGroupDetails>::value_type& pair)
+    {
+      return pair.first;
+    }
+  );
+
+  return g;
+}
+
+inline
+const HelpGroupDetails&
+Options::group_help(const std::string& group) const
+{
+  return m_help.at(group);
+}
+
+}
+
+#endif //CXXOPTS_HPP_INCLUDED
index 012fda16c4d92cb639a569f8b079f5e46c61b80d..3edddea94abb4c8ded40e525b1bb15ab283cb1cf 100644 (file)
@@ -31,4 +31,10 @@ foreach ex : threaded_examples
                install: false)
 endforeach
 
+if not platform.endswith('bsd') and platform != 'dragonfly'
+    executable('passthrough_hp', 'passthrough_hp.cc',
+               dependencies: [ thread_dep, libfuse_dep ],
+               install: false)
+endif
+
 # TODO: Link passthrough_fh with ulockmgr if available
diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc
new file mode 100644 (file)
index 0000000..dba8751
--- /dev/null
@@ -0,0 +1,1280 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+  Copyright (C) 2017       Nikolaus Rath <Nikolaus@rath.org>
+  Copyright (C) 2018       Valve, Inc
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+/** @file
+ *
+ * This is a "high-performance" version of passthrough_ll.c. While
+ * passthrough_ll.c is designed to be as simple as possible, this
+ * example intended to be as efficient and correct as possible.
+ *
+ * passthrough_hp.cc mirrors a specified "source" directory under a
+ * specified the mountpoint with as much fidelity and performance as
+ * possible.
+ *
+ * If --nocache is specified, the source directory may be changed
+ * directly even while mounted and the filesystem will continue
+ * to work correctly.
+ *
+ * Without --nocache, the source directory is assumed to be modified
+ * only through the passthrough filesystem. This enables much better
+ * performance, but if changes are made directly to the source, they
+ * may not be immediately visible under the mountpoint and further
+ * access to the mountpoint may result in incorrect behavior,
+ * including data-loss.
+ *
+ * On its own, this filesystem fulfills no practical purpose. It is
+ * intended as a template upon which additional functionality can be
+ * built.
+ *
+ * Unless --nocache is specified, is only possible to write to files
+ * for which the mounting user has read permissions. This is because
+ * the writeback cache requires the kernel to be able to issue read
+ * requests for all files (which the passthrough filesystem cannot
+ * satisfy if it can't read the file in the underlying filesystem).
+ *
+ * ## Source code ##
+ * \include passthrough_hp.cc
+ */
+
+#define FUSE_USE_VERSION 35
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+// C includes
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <ftw.h>
+#include <fuse_lowlevel.h>
+#include <inttypes.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/resource.h>
+#include <sys/xattr.h>
+#include <time.h>
+#include <unistd.h>
+#include <pthread.h>
+
+// C++ includes
+#include <cstddef>
+#include <cstdio>
+#include <cstdlib>
+#include <list>
+#include <cxxopts.hpp>
+#include <mutex>
+#include <fstream>
+#include <thread>
+#include <iomanip>
+
+using namespace std;
+
+/* We are re-using pointers to our `struct sfs_inode` and `struct
+   sfs_dirp` elements as inodes and file handles. This means that we
+   must be able to store pointer a pointer in both a fuse_ino_t
+   variable and a uint64_t variable (used for file handles). */
+static_assert(sizeof(fuse_ino_t) >= sizeof(void*),
+              "void* must fit into fuse_ino_t");
+static_assert(sizeof(fuse_ino_t) >= sizeof(uint64_t),
+              "fuse_ino_t must be at least 64 bits");
+
+
+/* Forward declarations */
+struct Inode;
+static Inode& get_inode(fuse_ino_t ino);
+static void forget_one(fuse_ino_t ino, uint64_t n);
+
+// Uniquely identifies a file in the source directory tree. This could
+// be simplified to just ino_t since we require the source directory
+// not to contain any mountpoints. This hasn't been done yet in case
+// we need to reconsider this constraint (but relaxing this would have
+// the drawback that we can no longer re-use inode numbers, and thus
+// readdir() would need to do a full lookup() in order to report the
+// right inode number).
+typedef std::pair<ino_t, dev_t> SrcId;
+
+// Define a hash function for SrcId
+namespace std {
+    template<>
+    struct hash<SrcId> {
+        size_t operator()(const SrcId& id) const {
+            return hash<ino_t>{}(id.first) ^ hash<dev_t>{}(id.second);
+        }
+    };
+}
+
+// Maps files in the source directory tree to inodes
+typedef std::unordered_map<SrcId, Inode> InodeMap;
+
+struct Inode {
+    int fd {-1};
+    bool is_symlink {false};
+    dev_t src_dev {0};
+    ino_t src_ino {0};
+    uint64_t nlookup {0};
+    std::mutex m;
+
+    // Delete copy constructor and assignments. We could implement
+    // move if we need it.
+    Inode() = default;
+    Inode(const Inode&) = delete;
+    Inode(Inode&& inode) = delete;
+    Inode& operator=(Inode&& inode) = delete;
+    Inode& operator=(const Inode&) = delete;
+
+    ~Inode() {
+        if(fd > 0)
+            close(fd);
+    }
+};
+
+struct Fs {
+    // Must be acquired *after* any Inode.m locks.
+    std::mutex mutex;
+    InodeMap inodes; // protected by mutex
+    Inode root;
+    double timeout;
+    bool debug;
+    std::string source;
+    size_t blocksize;
+    dev_t src_dev;
+    bool nosplice;
+    bool nocache;
+};
+static Fs fs{};
+
+
+#define FUSE_BUF_COPY_FLAGS                      \
+        (fs.nosplice ?                           \
+            FUSE_BUF_NO_SPLICE :                 \
+            static_cast<fuse_buf_copy_flags>(0))
+
+
+static Inode& get_inode(fuse_ino_t ino) {
+    if (ino == FUSE_ROOT_ID)
+        return fs.root;
+
+    Inode* inode = reinterpret_cast<Inode*>(ino);
+    if(inode->fd == -1) {
+        cerr << "INTERNAL ERROR: Unknown inode " << ino << endl;
+        abort();
+    }
+    return *inode;
+}
+
+
+static int get_fs_fd(fuse_ino_t ino) {
+    int fd = get_inode(ino).fd;
+    return fd;
+}
+
+
+static void sfs_init(void *userdata, fuse_conn_info *conn) {
+    (void)userdata;
+    if (conn->capable & FUSE_CAP_EXPORT_SUPPORT)
+        conn->want |= FUSE_CAP_EXPORT_SUPPORT;
+
+    if (fs.timeout && conn->capable & FUSE_CAP_WRITEBACK_CACHE)
+        conn->want |= FUSE_CAP_WRITEBACK_CACHE;
+
+    if (conn->capable & FUSE_CAP_FLOCK_LOCKS)
+        conn->want |= FUSE_CAP_FLOCK_LOCKS;
+
+    // Use splicing if supported. Since we are using writeback caching
+    // and readahead, individual requests should have a decent size so
+    // that splicing between fd's is well worth it.
+    if (conn->capable & FUSE_CAP_SPLICE_WRITE && !fs.nosplice)
+        conn->want |= FUSE_CAP_SPLICE_WRITE;
+    if (conn->capable & FUSE_CAP_SPLICE_READ && !fs.nosplice)
+        conn->want |= FUSE_CAP_SPLICE_READ;
+}
+
+
+static void sfs_getattr(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) {
+    (void)fi;
+    Inode& inode = get_inode(ino);
+    struct stat attr;
+    auto res = fstatat(inode.fd, "", &attr,
+                       AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+    if (res == -1) {
+        fuse_reply_err(req, errno);
+        return;
+    }
+    fuse_reply_attr(req, &attr, fs.timeout);
+}
+
+
+#ifdef HAVE_UTIMENSAT
+static int utimensat_empty_nofollow(Inode& inode,
+                                    const struct timespec *tv) {
+    if (inode.is_symlink) {
+        /* Does not work on current kernels, but may in the future:
+           https://marc.info/?l=linux-kernel&m=154158217810354&w=2 */
+        auto res = utimensat(inode.fd, "", tv, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+        if (res == -1 && errno == EINVAL) {
+            /* Sorry, no race free way to set times on symlink. */
+            errno = EPERM;
+        }
+        return res;
+    }
+
+    char procname[64];
+    sprintf(procname, "/proc/self/fd/%i", inode.fd);
+
+    return utimensat(AT_FDCWD, procname, tv, 0);
+}
+#endif
+
+
+static void do_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+                       int valid, struct fuse_file_info* fi) {
+    Inode& inode = get_inode(ino);
+    int ifd = inode.fd;
+    int res;
+
+    if (valid & FUSE_SET_ATTR_MODE) {
+        if (fi) {
+            res = fchmod(fi->fh, attr->st_mode);
+        } else {
+            char procname[64];
+            sprintf(procname, "/proc/self/fd/%i", ifd);
+            res = chmod(procname, attr->st_mode);
+        }
+        if (res == -1)
+            goto out_err;
+    }
+    if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+        uid_t uid = (valid & FUSE_SET_ATTR_UID) ? attr->st_uid : static_cast<uid_t>(-1);
+        gid_t gid = (valid & FUSE_SET_ATTR_GID) ? attr->st_gid : static_cast<gid_t>(-1);
+
+        res = fchownat(ifd, "", uid, gid, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+        if (res == -1)
+            goto out_err;
+    }
+    if (valid & FUSE_SET_ATTR_SIZE) {
+        if (fi) {
+            res = ftruncate(fi->fh, attr->st_size);
+        } else {
+            char procname[64];
+            sprintf(procname, "/proc/self/fd/%i", ifd);
+            res = truncate(procname, attr->st_size);
+        }
+        if (res == -1)
+            goto out_err;
+    }
+    if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+        struct timespec tv[2];
+
+        tv[0].tv_sec = 0;
+        tv[1].tv_sec = 0;
+        tv[0].tv_nsec = UTIME_OMIT;
+        tv[1].tv_nsec = UTIME_OMIT;
+
+        if (valid & FUSE_SET_ATTR_ATIME_NOW)
+            tv[0].tv_nsec = UTIME_NOW;
+        else if (valid & FUSE_SET_ATTR_ATIME)
+            tv[0] = attr->st_atim;
+
+        if (valid & FUSE_SET_ATTR_MTIME_NOW)
+            tv[1].tv_nsec = UTIME_NOW;
+        else if (valid & FUSE_SET_ATTR_MTIME)
+            tv[1] = attr->st_mtim;
+
+        if (fi)
+            res = futimens(fi->fh, tv);
+        else {
+#ifdef HAVE_UTIMENSAT
+            res = utimensat_empty_nofollow(inode, tv);
+#else
+            res = -1;
+            errno = EOPNOTSUPP;
+#endif
+        }
+        if (res == -1)
+            goto out_err;
+    }
+    return sfs_getattr(req, ino, fi);
+
+out_err:
+    fuse_reply_err(req, errno);
+}
+
+
+static void sfs_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+                        int valid, fuse_file_info *fi) {
+    (void) ino;
+    do_setattr(req, ino, attr, valid, fi);
+}
+
+
+static int do_lookup(fuse_ino_t parent, const char *name,
+                     fuse_entry_param *e) {
+    if (fs.debug)
+        cerr << "DEBUG: lookup(): name=" << name
+             << ", parent=" << parent << endl;
+    memset(e, 0, sizeof(*e));
+    e->attr_timeout = fs.timeout;
+    e->entry_timeout = fs.timeout;
+
+    auto newfd = openat(get_fs_fd(parent), name, O_PATH | O_NOFOLLOW);
+    if (newfd == -1)
+        return errno;
+
+    auto res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+    if (res == -1) {
+        auto saveerr = errno;
+        close(newfd);
+        if (fs.debug)
+            cerr << "DEBUG: lookup(): fstatat failed" << endl;
+        return saveerr;
+    }
+
+    if (e->attr.st_dev != fs.src_dev) {
+        cerr << "WARNING: Mountpoints in the source directory tree will be hidden." << endl;
+        return ENOTSUP;
+    } else if (e->attr.st_ino == FUSE_ROOT_ID) {
+        cerr << "ERROR: Source directory tree must not include inode "
+             << FUSE_ROOT_ID << endl;
+        return EIO;
+    }
+
+    SrcId id {e->attr.st_ino, e->attr.st_dev};
+    unique_lock<mutex> fs_lock {fs.mutex};
+    Inode* inode_p;
+    try {
+        inode_p = &fs.inodes[id];
+    } catch (std::bad_alloc&) {
+        return ENOMEM;
+    }
+    e->ino = reinterpret_cast<fuse_ino_t>(inode_p);
+    Inode& inode {*inode_p};
+
+    if(inode.fd != -1) { // found existing inode
+        fs_lock.unlock();
+        if (fs.debug)
+            cerr << "DEBUG: lookup(): inode " << e->attr.st_ino
+                 << " (userspace) already known." << endl;
+        lock_guard<mutex> g {inode.m};
+        inode.nlookup++;
+        close(newfd);
+    } else { // no existing inode
+        /* This is just here to make Helgrind happy. It violates the
+           lock ordering requirement (inode.m must be acquired before
+           fs.mutex), but this is of no consequence because at this
+           point no other thread has access to the inode mutex */
+        lock_guard<mutex> g {inode.m};
+        inode.src_ino = e->attr.st_ino;
+        inode.src_dev = e->attr.st_dev;
+        inode.is_symlink = S_ISLNK(e->attr.st_mode);
+        inode.nlookup = 1;
+        inode.fd = newfd;
+        fs_lock.unlock();
+
+        if (fs.debug)
+            cerr << "DEBUG: lookup(): created userspace inode " << e->attr.st_ino
+                 << endl;
+    }
+
+    return 0;
+}
+
+
+static void sfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) {
+    fuse_entry_param e {};
+    auto err = do_lookup(parent, name, &e);
+    if (err == ENOENT) {
+        e.attr_timeout = fs.timeout;
+        e.entry_timeout = fs.timeout;
+        e.ino = e.attr.st_ino = 0;
+        fuse_reply_entry(req, &e);
+    } else if (err) {
+        if (err == ENFILE || err == EMFILE)
+            cerr << "ERROR: Reached maximum number of file descriptors." << endl;
+        fuse_reply_err(req, err);
+    } else {
+        fuse_reply_entry(req, &e);
+    }
+}
+
+
+static void mknod_symlink(fuse_req_t req, fuse_ino_t parent,
+                              const char *name, mode_t mode, dev_t rdev,
+                              const char *link) {
+    int res;
+    Inode& inode_p = get_inode(parent);
+    auto saverr = ENOMEM;
+
+    if (S_ISDIR(mode))
+        res = mkdirat(inode_p.fd, name, mode);
+    else if (S_ISLNK(mode))
+        res = symlinkat(link, inode_p.fd, name);
+    else
+        res = mknodat(inode_p.fd, name, mode, rdev);
+    saverr = errno;
+    if (res == -1)
+        goto out;
+
+    fuse_entry_param e;
+    saverr = do_lookup(parent, name, &e);
+    if (saverr)
+        goto out;
+
+    fuse_reply_entry(req, &e);
+    return;
+
+out:
+    if (saverr == ENFILE || saverr == EMFILE)
+        cerr << "ERROR: Reached maximum number of file descriptors." << endl;
+    fuse_reply_err(req, saverr);
+}
+
+
+static void sfs_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
+                      mode_t mode, dev_t rdev) {
+    mknod_symlink(req, parent, name, mode, rdev, nullptr);
+}
+
+
+static void sfs_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+                      mode_t mode) {
+    mknod_symlink(req, parent, name, S_IFDIR | mode, 0, nullptr);
+}
+
+
+static void sfs_symlink(fuse_req_t req, const char *link, fuse_ino_t parent,
+                        const char *name) {
+    mknod_symlink(req, parent, name, S_IFLNK, 0, link);
+}
+
+
+static int linkat_empty_nofollow(Inode& inode, int dfd, const char *name) {
+    if (inode.is_symlink) {
+        auto res = linkat(inode.fd, "", dfd, name, AT_EMPTY_PATH);
+        if (res == -1 && (errno == ENOENT || errno == EINVAL)) {
+            /* Sorry, no race free way to hard-link a symlink. */
+            errno = EOPNOTSUPP;
+        }
+        return res;
+    }
+
+    char procname[64];
+    sprintf(procname, "/proc/self/fd/%i", inode.fd);
+    return linkat(AT_FDCWD, procname, dfd, name, AT_SYMLINK_FOLLOW);
+}
+
+
+static void sfs_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
+                     const char *name) {
+    Inode& inode = get_inode(ino);
+    Inode& inode_p = get_inode(parent);
+    fuse_entry_param e {};
+
+    e.attr_timeout = fs.timeout;
+    e.entry_timeout = fs.timeout;
+
+    auto res = linkat_empty_nofollow(inode, inode_p.fd, name);
+    if (res == -1) {
+        fuse_reply_err(req, errno);
+        return;
+    }
+
+    res = fstatat(inode.fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+    if (res == -1) {
+        fuse_reply_err(req, errno);
+        return;
+    }
+    e.ino = reinterpret_cast<fuse_ino_t>(&inode);
+    {
+        lock_guard<mutex> g {inode.m};
+        inode.nlookup++;
+    }
+
+    fuse_reply_entry(req, &e);
+    return;
+}
+
+
+static void sfs_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) {
+    Inode& inode_p = get_inode(parent);
+    lock_guard<mutex> g {inode_p.m};
+    auto res = unlinkat(inode_p.fd, name, AT_REMOVEDIR);
+    fuse_reply_err(req, res == -1 ? errno : 0);
+}
+
+
+static void sfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
+                       fuse_ino_t newparent, const char *newname,
+                       unsigned int flags) {
+    Inode& inode_p = get_inode(parent);
+    Inode& inode_np = get_inode(newparent);
+    if (flags) {
+        fuse_reply_err(req, EINVAL);
+        return;
+    }
+
+    auto res = renameat(inode_p.fd, name, inode_np.fd, newname);
+    fuse_reply_err(req, res == -1 ? errno : 0);
+}
+
+
+static void sfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) {
+    Inode& inode_p = get_inode(parent);
+    auto res = unlinkat(inode_p.fd, name, 0);
+    fuse_reply_err(req, res == -1 ? errno : 0);
+}
+
+
+static void forget_one(fuse_ino_t ino, uint64_t n) {
+    Inode& inode = get_inode(ino);
+    unique_lock<mutex> l {inode.m};
+
+    if(n > inode.nlookup) {
+        cerr << "INTERNAL ERROR: Negative lookup count for inode "
+             << inode.src_ino << endl;
+        abort();
+    }
+    inode.nlookup -= n;
+    if (!inode.nlookup) {
+        if (fs.debug)
+            cerr << "DEBUG: forget: cleaning up inode " << inode.src_ino << endl;
+        {
+            lock_guard<mutex> g_fs {fs.mutex};
+            l.unlock();
+            fs.inodes.erase({inode.src_ino, inode.src_dev});
+        }
+    } else if (fs.debug)
+            cerr << "DEBUG: forget: inode " << inode.src_ino
+                 << " lookup count now " << inode.nlookup << endl;
+}
+
+static void sfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) {
+    forget_one(ino, nlookup);
+    fuse_reply_none(req);
+}
+
+
+static void sfs_forget_multi(fuse_req_t req, size_t count,
+                             fuse_forget_data *forgets) {
+    for (int i = 0; i < count; i++)
+        forget_one(forgets[i].ino, forgets[i].nlookup);
+    fuse_reply_none(req);
+}
+
+
+static void sfs_readlink(fuse_req_t req, fuse_ino_t ino) {
+    Inode& inode = get_inode(ino);
+    char buf[PATH_MAX + 1];
+    auto res = readlinkat(inode.fd, "", buf, sizeof(buf));
+    if (res == -1)
+        fuse_reply_err(req, errno);
+    else if (res == sizeof(buf))
+        fuse_reply_err(req, ENAMETOOLONG);
+    else {
+        buf[res] = '\0';
+        fuse_reply_readlink(req, buf);
+    }
+}
+
+
+struct DirHandle {
+    DIR *dp {nullptr};
+    off_t offset;
+
+    DirHandle() = default;
+    DirHandle(const DirHandle&) = delete;
+    DirHandle& operator=(const DirHandle&) = delete;
+
+    ~DirHandle() {
+        if(dp)
+            closedir(dp);
+    }
+};
+
+
+static DirHandle *get_dir_handle(fuse_file_info *fi) {
+    return reinterpret_cast<DirHandle*>(fi->fh);
+}
+
+
+static void sfs_opendir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) {
+    Inode& inode = get_inode(ino);
+    auto d = new (nothrow) DirHandle;
+    if (d == nullptr) {
+        fuse_reply_err(req, ENOMEM);
+        return;
+    }
+
+    // Make Helgrind happy - it can't know that there's an implicit
+    // synchronization due to the fact that other threads cannot
+    // access d until we've called fuse_reply_*.
+    lock_guard<mutex> g {inode.m};
+
+    auto fd = openat(inode.fd, ".", O_RDONLY);
+    if (fd == -1)
+        goto out_errno;
+
+    // On success, dir stream takes ownership of fd, so we
+    // do not have to close it.
+    d->dp = fdopendir(fd);
+    if(d->dp == nullptr)
+        goto out_errno;
+
+    d->offset = 0;
+
+    fi->fh = reinterpret_cast<uint64_t>(d);
+    if(fs.timeout) {
+        fi->keep_cache = 1;
+        fi->cache_readdir = 1;
+    }
+    fuse_reply_open(req, fi);
+    return;
+
+out_errno:
+    auto error = errno;
+    delete d;
+    if (error == ENFILE || error == EMFILE)
+        cerr << "ERROR: Reached maximum number of file descriptors." << endl;
+    fuse_reply_err(req, error);
+}
+
+
+static bool is_dot_or_dotdot(const char *name) {
+    return name[0] == '.' &&
+           (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
+}
+
+
+static void do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+                    off_t offset, fuse_file_info *fi, int plus) {
+    auto d = get_dir_handle(fi);
+    Inode& inode = get_inode(ino);
+    lock_guard<mutex> g {inode.m};
+    char *p;
+    auto rem = size;
+    int err = 0, count = 0;
+
+    if (fs.debug)
+        cerr << "DEBUG: readdir(): started with offset "
+             << offset << endl;
+
+    auto buf = new (nothrow) char[size];
+    if (!buf) {
+        fuse_reply_err(req, ENOMEM);
+        return;
+    }
+    p = buf;
+
+    if (offset != d->offset) {
+        if (fs.debug)
+            cerr << "DEBUG: readdir(): seeking to " << offset << endl;
+        seekdir(d->dp, offset);
+        d->offset = offset;
+    }
+
+    while (1) {
+        struct dirent *entry;
+        errno = 0;
+        entry = readdir(d->dp);
+        if (!entry) {
+            if(errno) {
+                err = errno;
+                if (fs.debug)
+                    warn("DEBUG: readdir(): readdir failed with");
+                goto error;
+            }
+            break; // End of stream
+        }
+        d->offset = entry->d_off;
+        if (is_dot_or_dotdot(entry->d_name))
+            continue;
+
+        fuse_entry_param e{};
+        size_t entsize;
+        if(plus) {
+            err = do_lookup(ino, entry->d_name, &e);
+            if (err)
+                goto error;
+            entsize = fuse_add_direntry_plus(req, p, rem, entry->d_name, &e, entry->d_off);
+
+            if (entsize > rem) {
+                if (fs.debug)
+                    cerr << "DEBUG: readdir(): buffer full, returning data. " << endl;
+                forget_one(e.ino, 1);
+                break;
+            }
+        } else {
+            e.attr.st_ino = entry->d_ino;
+            e.attr.st_mode = entry->d_type << 12;
+            entsize = fuse_add_direntry(req, p, rem, entry->d_name, &e.attr, entry->d_off);
+
+            if (entsize > rem) {
+                if (fs.debug)
+                    cerr << "DEBUG: readdir(): buffer full, returning data. " << endl;
+                break;
+            }
+        }
+
+        p += entsize;
+        rem -= entsize;
+        count++;
+        if (fs.debug) {
+            cerr << "DEBUG: readdir(): added to buffer: " << entry->d_name
+                 << ", ino " << e.attr.st_ino << ", offset " << entry->d_off << endl;
+        }
+    }
+    err = 0;
+error:
+
+    // If there's an error, we can only signal it if we haven't stored
+    // any entries yet - otherwise we'd end up with wrong lookup
+    // counts for the entries that are already in the buffer. So we
+    // return what we've collected until that point.
+    if (err && rem == size) {
+        if (err == ENFILE || err == EMFILE)
+            cerr << "ERROR: Reached maximum number of file descriptors." << endl;
+        fuse_reply_err(req, err);
+    } else {
+        if (fs.debug)
+            cerr << "DEBUG: readdir(): returning " << count
+                 << " entries, curr offset " << d->offset << endl;
+        fuse_reply_buf(req, buf, size - rem);
+    }
+    delete[] buf;
+    return;
+}
+
+
+static void sfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+                        off_t offset, fuse_file_info *fi) {
+    // operation logging is done in readdir to reduce code duplication
+    do_readdir(req, ino, size, offset, fi, 0);
+}
+
+
+static void sfs_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+                            off_t offset, fuse_file_info *fi) {
+    // operation logging is done in readdir to reduce code duplication
+    do_readdir(req, ino, size, offset, fi, 1);
+}
+
+
+static void sfs_releasedir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) {
+    (void) ino;
+    auto d = get_dir_handle(fi);
+    delete d;
+    fuse_reply_err(req, 0);
+}
+
+
+static void sfs_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+                       mode_t mode, fuse_file_info *fi) {
+    Inode& inode_p = get_inode(parent);
+
+    auto fd = openat(inode_p.fd, name,
+                     (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
+    if (fd == -1) {
+        auto err = errno;
+        if (err == ENFILE || err == EMFILE)
+            cerr << "ERROR: Reached maximum number of file descriptors." << endl;
+        fuse_reply_err(req, err);
+        return;
+    }
+
+    fi->fh = fd;
+    fuse_entry_param e;
+    auto err = do_lookup(parent, name, &e);
+    if (err) {
+        if (err == ENFILE || err == EMFILE)
+            cerr << "ERROR: Reached maximum number of file descriptors." << endl;
+        fuse_reply_err(req, err);
+    } else
+        fuse_reply_create(req, &e, fi);
+}
+
+
+static void sfs_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+                         fuse_file_info *fi) {
+    (void) ino;
+    int res;
+    int fd = dirfd(get_dir_handle(fi)->dp);
+    if (datasync)
+        res = fdatasync(fd);
+    else
+        res = fsync(fd);
+    fuse_reply_err(req, res == -1 ? errno : 0);
+}
+
+
+static void sfs_open(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) {
+    Inode& inode = get_inode(ino);
+
+    /* With writeback cache, kernel may send read requests even
+       when userspace opened write-only */
+    if (fs.timeout && (fi->flags & O_ACCMODE) == O_WRONLY) {
+        fi->flags &= ~O_ACCMODE;
+        fi->flags |= O_RDWR;
+    }
+
+    /* With writeback cache, O_APPEND is handled by the kernel.  This
+       breaks atomicity (since the file may change in the underlying
+       filesystem, so that the kernel's idea of the end of the file
+       isn't accurate anymore). However, no process should modify the
+       file in the underlying filesystem once it has been read, so
+       this is not a problem. */
+    if (fs.timeout && fi->flags & O_APPEND)
+        fi->flags &= ~O_APPEND;
+
+    /* Unfortunately we cannot use inode.fd, because this was opened
+       with O_PATH (so it doesn't allow read/write access). */
+    char buf[64];
+    sprintf(buf, "/proc/self/fd/%i", inode.fd);
+    auto fd = open(buf, fi->flags & ~O_NOFOLLOW);
+    if (fd == -1) {
+        auto err = errno;
+        if (err == ENFILE || err == EMFILE)
+            cerr << "ERROR: Reached maximum number of file descriptors." << endl;
+        fuse_reply_err(req, err);
+        return;
+    }
+
+    fi->keep_cache = (fs.timeout != 0);
+    fi->fh = fd;
+    fuse_reply_open(req, fi);
+}
+
+
+static void sfs_release(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) {
+    (void) ino;
+    close(fi->fh);
+    fuse_reply_err(req, 0);
+}
+
+
+static void sfs_flush(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) {
+    (void) ino;
+    auto res = close(dup(fi->fh));
+    fuse_reply_err(req, res == -1 ? errno : 0);
+}
+
+
+static void sfs_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+                      fuse_file_info *fi) {
+    (void) ino;
+    int res;
+    if (datasync)
+        res = fdatasync(fi->fh);
+    else
+        res = fsync(fi->fh);
+    fuse_reply_err(req, res == -1 ? errno : 0);
+}
+
+
+static void do_read(fuse_req_t req, size_t size, off_t off, fuse_file_info *fi) {
+
+    fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+    buf.buf[0].flags = static_cast<fuse_buf_flags>(
+        FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK);
+    buf.buf[0].fd = fi->fh;
+    buf.buf[0].pos = off;
+
+    fuse_reply_data(req, &buf, FUSE_BUF_COPY_FLAGS);
+}
+
+static void sfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+                     fuse_file_info *fi) {
+    (void) ino;
+    do_read(req, size, off, fi);
+}
+
+
+static void do_write_buf(fuse_req_t req, size_t size, off_t off,
+                         fuse_bufvec *in_buf, fuse_file_info *fi) {
+    fuse_bufvec out_buf = FUSE_BUFVEC_INIT(size);
+    out_buf.buf[0].flags = static_cast<fuse_buf_flags>(
+        FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK);
+    out_buf.buf[0].fd = fi->fh;
+    out_buf.buf[0].pos = off;
+
+    auto res = fuse_buf_copy(&out_buf, in_buf, FUSE_BUF_COPY_FLAGS);
+    if (res < 0)
+        fuse_reply_err(req, -res);
+    else
+        fuse_reply_write(req, (size_t)res);
+}
+
+
+static void sfs_write_buf(fuse_req_t req, fuse_ino_t ino, fuse_bufvec *in_buf,
+                          off_t off, fuse_file_info *fi) {
+    (void) ino;
+    auto size {fuse_buf_size(in_buf)};
+    do_write_buf(req, size, off, in_buf, fi);
+}
+
+
+static void sfs_statfs(fuse_req_t req, fuse_ino_t ino) {
+    struct statvfs stbuf;
+
+    auto res = fstatvfs(get_fs_fd(ino), &stbuf);
+    if (res == -1)
+        fuse_reply_err(req, errno);
+    else
+        fuse_reply_statfs(req, &stbuf);
+}
+
+
+#ifdef HAVE_POSIX_FALLOCATE
+static void sfs_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+                          off_t offset, off_t length, fuse_file_info *fi) {
+    (void) ino;
+    if (mode) {
+        fuse_reply_err(req, EOPNOTSUPP);
+        return;
+    }
+
+    auto err = posix_fallocate(fi->fh, offset, length);
+    fuse_reply_err(req, err);
+}
+#endif
+
+static void sfs_flock(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi,
+                      int op) {
+    (void) ino;
+    auto res = flock(fi->fh, op);
+    fuse_reply_err(req, res == -1 ? errno : 0);
+}
+
+
+#ifdef HAVE_SETXATTR
+static void sfs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+                         size_t size) {
+    char *value = nullptr;
+    Inode& inode = get_inode(ino);
+    ssize_t ret;
+    int saverr;
+
+    if (inode.is_symlink) {
+        /* Sorry, no race free way to getxattr on symlink. */
+        saverr = ENOTSUP;
+        goto out;
+    }
+
+    char procname[64];
+    sprintf(procname, "/proc/self/fd/%i", inode.fd);
+
+    if (size) {
+        value = new (nothrow) char[size];
+        if (value == nullptr) {
+            saverr = ENOMEM;
+            goto out;
+        }
+
+        ret = getxattr(procname, name, value, size);
+        if (ret == -1)
+            goto out_err;
+        saverr = 0;
+        if (ret == 0)
+            goto out;
+
+        fuse_reply_buf(req, value, ret);
+    } else {
+        ret = getxattr(procname, name, nullptr, 0);
+        if (ret == -1)
+            goto out_err;
+
+        fuse_reply_xattr(req, ret);
+    }
+out_free:
+    delete[] value;
+    return;
+
+out_err:
+    saverr = errno;
+out:
+    fuse_reply_err(req, saverr);
+    goto out_free;
+}
+
+
+static void sfs_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) {
+    char *value = nullptr;
+    Inode& inode = get_inode(ino);
+    ssize_t ret;
+    int saverr;
+
+    if (inode.is_symlink) {
+        /* Sorry, no race free way to listxattr on symlink. */
+        saverr = ENOTSUP;
+        goto out;
+    }
+
+    char procname[64];
+    sprintf(procname, "/proc/self/fd/%i", inode.fd);
+
+    if (size) {
+        value = new (nothrow) char[size];
+        if (value == nullptr) {
+            saverr = ENOMEM;
+            goto out;
+        }
+
+        ret = listxattr(procname, value, size);
+        if (ret == -1)
+            goto out_err;
+        saverr = 0;
+        if (ret == 0)
+            goto out;
+
+        fuse_reply_buf(req, value, ret);
+    } else {
+        ret = listxattr(procname, nullptr, 0);
+        if (ret == -1)
+            goto out_err;
+
+        fuse_reply_xattr(req, ret);
+    }
+out_free:
+    delete[] value;
+    return;
+out_err:
+    saverr = errno;
+out:
+    fuse_reply_err(req, saverr);
+    goto out_free;
+}
+
+
+static void sfs_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+                         const char *value, size_t size, int flags) {
+    Inode& inode = get_inode(ino);
+    ssize_t ret;
+    int saverr;
+
+    if (inode.is_symlink) {
+        /* Sorry, no race free way to setxattr on symlink. */
+        saverr = ENOTSUP;
+        goto out;
+    }
+
+    char procname[64];
+    sprintf(procname, "/proc/self/fd/%i", inode.fd);
+
+    ret = setxattr(procname, name, value, size, flags);
+    saverr = ret == -1 ? errno : 0;
+
+out:
+    fuse_reply_err(req, saverr);
+}
+
+
+static void sfs_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name) {
+    char procname[64];
+    Inode& inode = get_inode(ino);
+    ssize_t ret;
+    int saverr;
+
+    if (inode.is_symlink) {
+        /* Sorry, no race free way to setxattr on symlink. */
+        saverr = ENOTSUP;
+        goto out;
+    }
+
+    sprintf(procname, "/proc/self/fd/%i", inode.fd);
+    ret = removexattr(procname, name);
+    saverr = ret == -1 ? errno : 0;
+
+out:
+    fuse_reply_err(req, saverr);
+}
+#endif
+
+
+static void assign_operations(fuse_lowlevel_ops &sfs_oper) {
+    sfs_oper.init = sfs_init;
+    sfs_oper.lookup = sfs_lookup;
+    sfs_oper.mkdir = sfs_mkdir;
+    sfs_oper.mknod = sfs_mknod;
+    sfs_oper.symlink = sfs_symlink;
+    sfs_oper.link = sfs_link;
+    sfs_oper.unlink = sfs_unlink;
+    sfs_oper.rmdir = sfs_rmdir;
+    sfs_oper.rename = sfs_rename;
+    sfs_oper.forget = sfs_forget;
+    sfs_oper.forget_multi = sfs_forget_multi;
+    sfs_oper.getattr = sfs_getattr;
+    sfs_oper.setattr = sfs_setattr;
+    sfs_oper.readlink = sfs_readlink;
+    sfs_oper.opendir = sfs_opendir;
+    sfs_oper.readdir = sfs_readdir;
+    sfs_oper.readdirplus = sfs_readdirplus;
+    sfs_oper.releasedir = sfs_releasedir;
+    sfs_oper.fsyncdir = sfs_fsyncdir;
+    sfs_oper.create = sfs_create;
+    sfs_oper.open = sfs_open;
+    sfs_oper.release = sfs_release;
+    sfs_oper.flush = sfs_flush;
+    sfs_oper.fsync = sfs_fsync;
+    sfs_oper.read = sfs_read;
+    sfs_oper.write_buf = sfs_write_buf;
+    sfs_oper.statfs = sfs_statfs;
+#ifdef HAVE_POSIX_FALLOCATE
+    sfs_oper.fallocate = sfs_fallocate;
+#endif
+    sfs_oper.flock = sfs_flock;
+#ifdef HAVE_SETXATTR
+    sfs_oper.setxattr = sfs_setxattr;
+    sfs_oper.getxattr = sfs_getxattr;
+    sfs_oper.listxattr = sfs_listxattr;
+    sfs_oper.removexattr = sfs_removexattr;
+#endif
+}
+
+static void print_usage(char *prog_name) {
+    cout << "Usage: " << prog_name << " --help\n"
+         << "       " << prog_name << " [options] <source> <mountpoint>\n";
+}
+
+static cxxopts::ParseResult parse_wrapper(cxxopts::Options& parser, int& argc, char**& argv) {
+    try {
+        return parser.parse(argc, argv);
+    } catch (cxxopts::option_not_exists_exception& exc) {
+        std::cout << argv[0] << ": " << exc.what() << std::endl;
+        print_usage(argv[0]);
+        exit(2);
+    }
+}
+
+
+static cxxopts::ParseResult parse_options(int argc, char **argv) {
+    cxxopts::Options opt_parser(argv[0]);
+    opt_parser.add_options()
+        ("debug", "Enable filesystem debug messages")
+        ("debug-fuse", "Enable libfuse debug messages")
+        ("help", "Print help")
+        ("nocache", "Disable all caching")
+        ("nosplice", "Do not use splice(2) to transfer data")
+        ("single", "Run single-threaded");
+
+    // FIXME: Find a better way to limit the try clause to just
+    // opt_parser.parse() (cf. https://github.com/jarro2783/cxxopts/issues/146)
+    auto options = parse_wrapper(opt_parser, argc, argv);
+
+    if (options.count("help")) {
+        print_usage(argv[0]);
+        // Strip everything before the option list from the
+        // default help string.
+        auto help = opt_parser.help();
+        std::cout << std::endl << "options:"
+                  << help.substr(help.find("\n\n") + 1, string::npos);
+        exit(0);
+
+    } else if (argc != 3) {
+        std::cout << argv[0] << ": invalid number of arguments\n";
+        print_usage(argv[0]);
+        exit(2);
+    }
+
+    fs.debug = options.count("debug") != 0;
+    fs.nosplice = options.count("nosplice") != 0;
+    fs.source = std::string {realpath(argv[1], NULL)};
+
+    return options;
+}
+
+
+static void maximize_fd_limit() {
+    struct rlimit lim {};
+    auto res = getrlimit(RLIMIT_NOFILE, &lim);
+    if (res != 0) {
+        warn("WARNING: getrlimit() failed with");
+        return;
+    }
+    lim.rlim_cur = lim.rlim_max;
+    res = setrlimit(RLIMIT_NOFILE, &lim);
+    if (res != 0)
+        warn("WARNING: setrlimit() failed with");
+}
+
+
+int main(int argc, char *argv[]) {
+
+    // Parse command line options
+    auto options {parse_options(argc, argv)};
+
+    // We need an fd for every dentry in our the filesystem that the
+    // kernel knows about. This is way more than most processes need,
+    // so try to get rid of any resource softlimit.
+    maximize_fd_limit();
+
+    // Initialize filesystem root
+    fs.root.fd = -1;
+    fs.root.nlookup = 9999;
+    fs.root.is_symlink = false;
+    fs.timeout = options.count("nocache") ? 0 : 86400.0;
+
+    struct stat stat;
+    auto ret = lstat(fs.source.c_str(), &stat);
+    if (ret == -1)
+        err(1, "ERROR: failed to stat source (\"%s\")", fs.source.c_str());
+    if (!S_ISDIR(stat.st_mode))
+        errx(1, "ERROR: source is not a directory");
+    fs.src_dev = stat.st_dev;
+
+    fs.root.fd = open(fs.source.c_str(), O_PATH);
+    if (fs.root.fd == -1)
+        err(1, "ERROR: open(\"%s\", O_PATH)", fs.source.c_str());
+
+    // Initialize fuse
+    fuse_args args = FUSE_ARGS_INIT(0, nullptr);
+    if (fuse_opt_add_arg(&args, argv[0]) ||
+        fuse_opt_add_arg(&args, "-o") ||
+        fuse_opt_add_arg(&args, "default_permissions,fsname=hpps") ||
+        (options.count("debug-fuse") && fuse_opt_add_arg(&args, "-odebug")))
+        errx(3, "ERROR: Out of memory");
+
+    fuse_lowlevel_ops sfs_oper {};
+    assign_operations(sfs_oper);
+    auto se = fuse_session_new(&args, &sfs_oper, sizeof(sfs_oper), &fs);
+    if (se == nullptr)
+        goto err_out1;
+
+    if (fuse_set_signal_handlers(se) != 0)
+        goto err_out2;
+
+    // Don't apply umask, use modes exactly as specified
+    umask(0);
+
+    // Mount and run main loop
+    struct fuse_loop_config loop_config;
+    loop_config.clone_fd = 0;
+    loop_config.max_idle_threads = 10;
+    if (fuse_session_mount(se, argv[2]) != 0)
+        goto err_out3;
+    if (options.count("single"))
+        ret = fuse_session_loop(se);
+    else
+        ret = fuse_session_loop_mt(se, &loop_config);
+
+    fuse_session_unmount(se);
+
+err_out3:
+    fuse_remove_signal_handlers(se);
+err_out2:
+    fuse_session_destroy(se);
+err_out1:
+    fuse_opt_free_args(&args);
+
+    return ret ? 1 : 0;
+}
+
index 5797fec3fa7eff51efe375e56154ab8765880ac0..c51d869557c6d8dc55d8d7e158206b7d9991f8a2 100644 (file)
@@ -1,4 +1,4 @@
-project('libfuse3', 'c', version: '3.5.0',
+project('libfuse3', ['cpp', 'c'], version: '3.5.0',
         meson_version: '>= 0.42',
         default_options: [ 'buildtype=debugoptimized' ])
 
@@ -66,6 +66,10 @@ configure_file(output: 'config.h',
 add_project_arguments('-D_REENTRANT', '-DHAVE_CONFIG_H', '-Wall', '-Wextra', '-Wno-sign-compare',
                       '-Wstrict-prototypes', '-Wmissing-declarations', '-Wwrite-strings',
                       '-fno-strict-aliasing', language: 'c')
+add_project_arguments('-D_REENTRANT', '-DHAVE_CONFIG_H', '-D_GNU_SOURCE',
+                     '-Wall', '-Wextra', '-Wno-sign-compare', '-std=c++11',
+                     '-Wmissing-declarations', '-Wwrite-strings',
+                     '-fno-strict-aliasing', language: 'cpp')
 
 # Some (stupid) GCC versions warn about unused return values even when they are
 # casted to void. This makes -Wunused-result pretty useless, since there is no
index 1b98a7c7176da2cc92cc2267ac95d183445072f8..3aabd19cf8bac8ba6d8db497e9e12c6417e579fa 100755 (executable)
@@ -153,6 +153,60 @@ def test_passthrough(tmpdir, name, debug, capfd, writeback):
     else:
         umount(mount_process, mnt_dir)
 
+@pytest.mark.parametrize("cache", (False, True))
+def test_passthrough_hp(tmpdir, cache):
+    mnt_dir = str(tmpdir.mkdir('mnt'))
+    src_dir = str(tmpdir.mkdir('src'))
+
+    cmdline = base_cmdline + \
+              [ pjoin(basename, 'example', 'passthrough_hp'),
+                src_dir, mnt_dir ]
+
+    if not cache:
+        cmdline.append('--nocache')
+        
+    mount_process = subprocess.Popen(cmdline)
+    try:
+        wait_for_mount(mount_process, mnt_dir)
+
+        tst_statvfs(mnt_dir)
+        tst_readdir(src_dir, mnt_dir)
+        tst_readdir_big(src_dir, mnt_dir)
+        tst_open_read(src_dir, mnt_dir)
+        tst_open_write(src_dir, mnt_dir)
+        tst_create(mnt_dir)
+        tst_passthrough(src_dir, mnt_dir)
+        tst_append(src_dir, mnt_dir)
+        tst_seek(src_dir, mnt_dir)
+        tst_mkdir(mnt_dir)
+        tst_rmdir(src_dir, mnt_dir)
+        tst_unlink(src_dir, mnt_dir)
+        tst_symlink(mnt_dir)
+        if os.getuid() == 0:
+            tst_chown(mnt_dir)
+
+        # Underlying fs may not have full nanosecond resolution
+        tst_utimens(mnt_dir, ns_tol=1000)
+
+        tst_link(mnt_dir)
+        tst_truncate_path(mnt_dir)
+        tst_truncate_fd(mnt_dir)
+        tst_open_unlink(mnt_dir)
+
+        # test_syscalls assumes that changes in source directory
+        # will be reflected immediately in mountpoint, so we
+        # can't use it.
+        if not cache:
+            syscall_test_cmd = [ os.path.join(basename, 'test', 'test_syscalls'),
+                             mnt_dir, ':' + src_dir ]
+            subprocess.check_call(syscall_test_cmd)
+    except:
+        cleanup(mount_process, mnt_dir)
+        raise
+    else:
+        umount(mount_process, mnt_dir)
+
+        
 @pytest.mark.skipif(fuse_proto < (7,11),
                     reason='not supported by running kernel')
 def test_ioctl(tmpdir):
index db4be566d60b91ff8e845ff58b69141f59d72926..a7e2bc7732ba6245bf75a8345fbe53814697ab13 100644 (file)
@@ -67,6 +67,11 @@ static void test_error(const char *func, const char *msg, ...)
        fprintf(stderr, "\n");
 }
 
+static int is_dot_or_dotdot(const char *name) {
+    return name[0] == '.' &&
+           (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
+}
+
 static void success(void)
 {
        fprintf(stderr, "%s OK\n", testname);
@@ -381,10 +386,6 @@ static int check_dir_contents(const char *path, const char **contents)
                found[i] = 0;
                cont[i] = contents[i];
        }
-       found[i] = 0;
-       cont[i++] = ".";
-       found[i] = 0;
-       cont[i++] = "..";
        cont[i] = NULL;
 
        dp = opendir(path);
@@ -405,6 +406,8 @@ static int check_dir_contents(const char *path, const char **contents)
                        }
                        break;
                }
+               if (is_dot_or_dotdot(de->d_name))
+                       continue;
                for (i = 0; cont[i] != NULL; i++) {
                        assert(i < MAX_ENTRIES);
                        if (strcmp(cont[i], de->d_name) == 0) {
index dc4612085bb0bd27e88c78db3bd774c5fa10fe4f..b2f36102f2f1879facccc6ad8eb353fd3898d39e 100755 (executable)
@@ -27,12 +27,15 @@ cd "${TEST_DIR}"
 # Standard build
 for CC in gcc gcc-7 clang; do
     mkdir build-${CC}; cd build-${CC}
+    if [ "${CC}" == "clang" ]; then
+        export CXX="clang++"
+    fi
     if [ ${CC} == 'gcc-7' ]; then
         build_opts='-D b_lundef=false'
     else
         build_opts=''
     fi
-    meson -D werror=true ${build_opts} "${SOURCE_DIR}"
+    meson -D werror=true ${build_opts} "${SOURCE_DIR}" || (cat meson-logs/meson-log.txt; false)
     ninja
 
     sudo chown root:root util/fusermount3
@@ -44,11 +47,13 @@ done
 
 # Sanitized build
 CC=clang
+CXX=clang++
 for san in undefined address; do
     mkdir build-${san}; cd build-${san}
     # b_lundef=false is required to work around clang
     # bug, cf. https://groups.google.com/forum/#!topic/mesonbuild/tgEdAXIIdC4
-    meson -D b_sanitize=${san} -D b_lundef=false -D werror=true "${SOURCE_DIR}"
+    meson -D b_sanitize=${san} -D b_lundef=false -D werror=true "${SOURCE_DIR}" \
+           || (cat meson-logs/meson-log.txt; false)
     ninja
 
     # Test as root and regular user