fd: Add MMap handle object

This makes it possible to have an RAII managed MMap

Change-Id: Id61cea650a68087ca9d263a57dd780791845eb85
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/meson.build b/src/meson.build
index 9b4ad23..af7399c 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -57,6 +57,7 @@
     'stdplus/fd/dupable.cpp',
     'stdplus/fd/impl.cpp',
     'stdplus/fd/managed.cpp',
+    'stdplus/fd/mmap.cpp',
     'stdplus/fd/ops.cpp',
   ]
 
@@ -67,6 +68,7 @@
     'stdplus/fd/impl.hpp',
     'stdplus/fd/intf.hpp',
     'stdplus/fd/managed.hpp',
+    'stdplus/fd/mmap.hpp',
     'stdplus/fd/ops.hpp',
     subdir: 'stdplus/fd')
 elif get_option('fd').enabled()
diff --git a/src/stdplus/fd/gmock.hpp b/src/stdplus/fd/gmock.hpp
index 97a4f01..ca3b2af 100644
--- a/src/stdplus/fd/gmock.hpp
+++ b/src/stdplus/fd/gmock.hpp
@@ -35,6 +35,11 @@
     MOCK_METHOD(FdFlags, fcntlGetfd, (), (const, override));
     MOCK_METHOD(void, fcntlSetfl, (FileFlags flags), (override));
     MOCK_METHOD(FileFlags, fcntlGetfl, (), (const, override));
+    MOCK_METHOD(std::span<std::byte>, mmap,
+                (std::span<std::byte> window, ProtFlags prot, MMapFlags flags,
+                 off_t offset),
+                (override));
+    MOCK_METHOD(void, munmap, (std::span<std::byte> window), (override));
 };
 
 } // namespace fd
diff --git a/src/stdplus/fd/impl.cpp b/src/stdplus/fd/impl.cpp
index 2d92b72..34b365f 100644
--- a/src/stdplus/fd/impl.cpp
+++ b/src/stdplus/fd/impl.cpp
@@ -179,5 +179,22 @@
     return FileFlags(CHECK_ERRNO(::fcntl(get(), F_GETFL), "fcntl getfl"));
 }
 
+std::span<std::byte> FdImpl::mmap(std::span<std::byte> window, ProtFlags prot,
+                                  MMapFlags flags, off_t offset)
+{
+    auto ret = ::mmap(window.data(), window.size(), static_cast<int>(prot),
+                      static_cast<int>(flags), get(), offset);
+    if (ret == MAP_FAILED)
+    {
+        util::doError(errno, "mmap");
+    }
+    return {reinterpret_cast<std::byte*>(ret), window.size()};
+}
+
+void FdImpl::munmap(std::span<std::byte> window)
+{
+    CHECK_ERRNO(::munmap(window.data(), window.size()), "munmap");
+}
+
 } // namespace fd
 } // namespace stdplus
diff --git a/src/stdplus/fd/impl.hpp b/src/stdplus/fd/impl.hpp
index b4e134c..9d5acaf 100644
--- a/src/stdplus/fd/impl.hpp
+++ b/src/stdplus/fd/impl.hpp
@@ -31,6 +31,11 @@
     FdFlags fcntlGetfd() const override;
     void fcntlSetfl(FileFlags flags) override;
     FileFlags fcntlGetfl() const override;
+
+  protected:
+    std::span<std::byte> mmap(std::span<std::byte> window, ProtFlags prot,
+                              MMapFlags flags, off_t offset) override;
+    void munmap(std::span<std::byte> window) override;
 };
 
 } // namespace fd
diff --git a/src/stdplus/fd/intf.hpp b/src/stdplus/fd/intf.hpp
index 7778ae1..4d9553d 100644
--- a/src/stdplus/fd/intf.hpp
+++ b/src/stdplus/fd/intf.hpp
@@ -4,6 +4,7 @@
 #include <optional>
 #include <span>
 #include <stdplus/flags.hpp>
+#include <sys/mman.h>
 #include <sys/socket.h>
 #include <tuple>
 
@@ -80,6 +81,40 @@
 };
 using FileFlags = BitFlags<int, FileFlag>;
 
+enum class ProtFlag : int
+{
+    Exec = PROT_EXEC,
+    Read = PROT_READ,
+    Write = PROT_WRITE,
+};
+using ProtFlags = BitFlags<int, ProtFlag>;
+
+enum class MMapAccess : int
+{
+    Shared = MAP_SHARED,
+    Private = MAP_PRIVATE,
+};
+
+enum class MMapFlag : int
+{
+};
+
+class MMapFlags : public BitFlags<int, MMapFlag>
+{
+  public:
+    inline MMapFlags(MMapAccess access) :
+        BitFlags<int, MMapFlag>(static_cast<int>(access))
+    {
+    }
+
+    inline MMapFlags(BitFlags<int, MMapFlag> flags) :
+        BitFlags<int, MMapFlag>(flags)
+    {
+    }
+};
+
+class MMap;
+
 class Fd
 {
   public:
@@ -106,6 +141,13 @@
     virtual FdFlags fcntlGetfd() const = 0;
     virtual void fcntlSetfl(FileFlags flags) = 0;
     virtual FileFlags fcntlGetfl() const = 0;
+
+  protected:
+    virtual std::span<std::byte> mmap(std::span<std::byte> window,
+                                      ProtFlags prot, MMapFlags flags,
+                                      off_t offset) = 0;
+    virtual void munmap(std::span<std::byte> window) = 0;
+    friend class MMap;
 };
 
 } // namespace fd
diff --git a/src/stdplus/fd/mmap.cpp b/src/stdplus/fd/mmap.cpp
new file mode 100644
index 0000000..079815d
--- /dev/null
+++ b/src/stdplus/fd/mmap.cpp
@@ -0,0 +1,26 @@
+#include <stdplus/fd/mmap.hpp>
+#include <stdplus/util/cexec.hpp>
+
+namespace stdplus
+{
+namespace fd
+{
+
+MMap::MMap(Fd& fd, std::span<std::byte> window, ProtFlags prot, MMapFlags flags,
+           off_t offset) :
+    mapping(fd.mmap(window, prot, flags, offset), fd)
+{
+}
+
+std::span<std::byte> MMap::get() const
+{
+    return *mapping;
+}
+
+void MMap::drop(std::span<std::byte>&& mapping, std::reference_wrapper<Fd>& fd)
+{
+    fd.get().munmap(mapping);
+}
+
+} // namespace fd
+} // namespace stdplus
diff --git a/src/stdplus/fd/mmap.hpp b/src/stdplus/fd/mmap.hpp
new file mode 100644
index 0000000..06b7944
--- /dev/null
+++ b/src/stdplus/fd/mmap.hpp
@@ -0,0 +1,36 @@
+#pragma once
+#include <cstddef>
+#include <functional>
+#include <span>
+#include <stdplus/fd/intf.hpp>
+#include <stdplus/handle/managed.hpp>
+
+namespace stdplus
+{
+namespace fd
+{
+
+class MMap
+{
+  public:
+    inline MMap(Fd& fd, size_t window_size, ProtFlags prot, MMapFlags flags,
+                off_t offset) :
+        MMap(
+            fd,
+            std::span<std::byte>{static_cast<std::byte*>(nullptr), window_size},
+            prot, flags, offset)
+    {
+    }
+    MMap(Fd& fd, std::span<std::byte> window, ProtFlags prot, MMapFlags flags,
+         off_t offset);
+
+    std::span<std::byte> get() const;
+
+  private:
+    static void drop(std::span<std::byte>&&, std::reference_wrapper<Fd>&);
+    Managed<std::span<std::byte>, std::reference_wrapper<Fd>>::Handle<drop>
+        mapping;
+};
+
+} // namespace fd
+} // namespace stdplus
diff --git a/test/fd/mmap.cpp b/test/fd/mmap.cpp
new file mode 100644
index 0000000..3f1ab33
--- /dev/null
+++ b/test/fd/mmap.cpp
@@ -0,0 +1,26 @@
+#include <array>
+#include <gtest/gtest.h>
+#include <stdplus/fd/create.hpp>
+#include <stdplus/fd/mmap.hpp>
+
+namespace stdplus
+{
+namespace fd
+{
+
+TEST(MMap, Basic)
+{
+    auto fd = open("/dev/zero", OpenAccess::ReadOnly);
+    auto map = MMap(fd, 32, ProtFlags().set(ProtFlag::Read),
+                    MMapFlags{MMapAccess::Private}, 0);
+    auto sp = map.get();
+    ASSERT_NE(nullptr, sp.data());
+    ASSERT_EQ(32, sp.size());
+    for (size_t i = 0; i < 32; ++i)
+    {
+        EXPECT_EQ(sp[i], std::byte{});
+    }
+}
+
+} // namespace fd
+} // namespace stdplus
diff --git a/test/meson.build b/test/meson.build
index 7d26297..2634c45 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -45,6 +45,7 @@
     'fd/managed',
     'fd/intf',
     'fd/impl',
+    'fd/mmap',
     'fd/mock',
     'fd/ops',
   ]