Skip to content

Commit 3893a24

Browse files
Add ipr::input::SourceFile (#307)
The data type `ipr::input::SourceFile` abstracts away the ceremonies of memory mapping an input source file into memory as a sequence of raw bytes.
1 parent b6582d2 commit 3893a24

File tree

4 files changed

+151
-2
lines changed

4 files changed

+151
-2
lines changed

CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
cmake_minimum_required(
2-
VERSION 3.24
2+
VERSION 3.28
33
)
44
include(CheckIPOSupported)
55

@@ -30,6 +30,7 @@ add_library(${PROJECT_NAME}
3030
src/impl.cxx
3131
src/io.cxx
3232
src/traversal.cxx
33+
src/input.cxx
3334
src/utility.cxx
3435
)
3536

@@ -52,6 +53,7 @@ target_sources(${PROJECT_NAME}
5253
${PROJECT_SOURCE_DIR}/include/ipr/synopsis
5354
${PROJECT_SOURCE_DIR}/include/ipr/traversal
5455
${PROJECT_SOURCE_DIR}/include/ipr/utility
56+
${PROJECT_SOURCE_DIR}/include/ipr/input
5557
${PROJECT_SOURCE_DIR}/3rdparty/doctest/doctest.h
5658
)
5759

include/ipr/input

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// -*- C++ -*-
2+
//
3+
// This file is part of The Pivot framework.
4+
// Written by Gabriel Dos Reis.
5+
// See LICENSE for copyright and license notices.
6+
//
7+
8+
#include <cstddef>
9+
#include <span>
10+
#include <filesystem>
11+
12+
namespace ipr::input {
13+
// Type for the error code values used by the host OS.
14+
#ifdef _WIN32
15+
using ErrorCode = DWORD;
16+
#else
17+
using ErrorCode = int;
18+
#endif
19+
20+
// String type preferred by the host OS to specify pathnames.
21+
using SystemPath = std::filesystem::path::string_type;
22+
23+
// Exception type used to signal inability of the host OS to access a file.
24+
struct AccessError {
25+
SystemPath path;
26+
ErrorCode error_code;
27+
};
28+
29+
// Exception type used to signal the file designated by th `path` is not a regular file.
30+
struct RegularFileError {
31+
SystemPath path;
32+
};
33+
34+
// Exception type used to signal inability of the host OS to memory-map a file.
35+
struct FileMappingError {
36+
SystemPath path;
37+
ErrorCode error_code;
38+
};
39+
40+
// Input source file mapped to memory as sequence of raw bytes.
41+
struct SourceFile {
42+
using View = std::span<const std::byte>;
43+
44+
explicit SourceFile(const SystemPath&);
45+
SourceFile(SourceFile&&);
46+
~SourceFile();
47+
View bytes() const { return view; }
48+
private:
49+
View view;
50+
};
51+
}

src/input.cxx

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//
2+
// This file is part of The Pivot framework.
3+
// Written by Gabriel Dos Reis.
4+
// See LICENSE for copright and license notices.
5+
//
6+
7+
#ifdef _WIN32
8+
# include <windows.h>
9+
#else
10+
# include <sys/stat.h>
11+
# include <sys/mman.h>
12+
# include <fcntl.h>
13+
# include <unistd.h>
14+
#endif
15+
16+
#include <ipr/input>
17+
18+
namespace ipr::input {
19+
#ifdef _WIN32
20+
// Helper type for automatically closing a handle on scope exit.
21+
struct SystemHandle {
22+
SystemHandle(HANDLE h) : handle{h} { }
23+
bool valid() const { return handle != INVALID_HANDLE_VALUE; }
24+
auto get_handle() const { return handle; }
25+
~SystemHandle()
26+
{
27+
if (valid())
28+
CloseHandle(handle);
29+
}
30+
private:
31+
HANDLE handle;
32+
};
33+
#endif
34+
35+
SourceFile::SourceFile(const SystemPath& path)
36+
{
37+
#ifdef _WIN32
38+
// FIXME: Handle the situation of large files in a 32-bit program.
39+
static_assert(sizeof(LARGE_INTEGER) == sizeof(std::size_t));
40+
41+
SystemHandle file = CreateFileW(path.c_str(), GENERIC_READ, 0, nullptr,
42+
OPEN_EXISTING,
43+
FILE_ATTRIBUTE_NORMAL, nullptr);
44+
if (not file.valid())
45+
throw AccessError{ path, GetLastError() };
46+
LARGE_INTEGER s { };
47+
if (not GetFileSizeEx(file.get_handle(), &s))
48+
throw AccessError{ path, GetLastError() };
49+
if (s.QuadPart)
50+
return;
51+
SystemHandle mapping = CreateFileMapping(file.get_handle(), nullptr, PAGE_READONLY, 0, 0, nullptr);
52+
if (mapping.get_handle() == nullptr)
53+
throw FileMappingError{ path, GetLastError() };
54+
auto start = MapViewOfFile(mapping.get_handle(), FILE_MAP_READ, 0, 0, 0);
55+
view = { reinterpret_cast<const std::byte*>(start), static_cast<View::size_type>(s.QuadPart) };
56+
#else
57+
struct stat s { };
58+
errno = 0;
59+
if (stat(path.c_str(), &s) < 0)
60+
throw AccessError{ path, errno };
61+
else if (not S_ISREG(s.st_mode))
62+
throw RegularFileError{ path };
63+
64+
// Don't labor too hard with empty files.
65+
if (s.st_size == 0)
66+
return;
67+
68+
auto fd = open(path.c_str(), O_RDONLY);
69+
if (fd < 0)
70+
throw AccessError{ path, errno };
71+
auto start = mmap(nullptr, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
72+
close(fd);
73+
if (start == MAP_FAILED)
74+
throw FileMappingError{ path };
75+
view = { reinterpret_cast<std::byte*>(start), static_cast<View::size_type>(s.st_size) };
76+
#endif
77+
}
78+
79+
SourceFile::SourceFile(SourceFile&& src) : view{src.view}
80+
{
81+
src.view = { };
82+
}
83+
84+
SourceFile::~SourceFile()
85+
{
86+
if (not view.empty())
87+
{
88+
#ifdef _WIN32
89+
UnmapViewOfFile(view.data());
90+
#else
91+
munmap(const_cast<std::byte*>(view.data()), view.size());
92+
#endif
93+
}
94+
}
95+
}

tests/unit-tests/specifiers.cxx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <sstream>
66
#include <vector>
77
#include <algorithm>
8+
#include <ranges>
89
#include <ipr/impl>
910
#include <ipr/io>
1011
#include <ipr/utility>
@@ -61,7 +62,7 @@ TEST_CASE("random combination of basic specifiers") {
6162
const auto sample_size = 1 + std::rand() % spec_count;
6263
std::vector<std::u8string_view> test;
6364
ipr::Specifiers specifiers { };
64-
for (int i = 0; i < sample_size; ++i) {
65+
for (auto i[[maybe_unused]] : std::views::iota(0u, sample_size)) {
6566
auto w = specs[std::rand() % sample_size];
6667
// Only add new specifier.
6768
if (std::find(test.begin(), test.end(), w) < test.end())

0 commit comments

Comments
 (0)