├── .gitignore ├── clangformat.bash ├── src ├── main.cpp └── count_lines.cpp ├── include └── lc │ ├── count_lines.hpp │ ├── argparse.hpp │ └── mio.hpp ├── utils └── make_large_file.py ├── README.md └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /clangformat.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | find ./include ./src -type f \( -iname \*.cpp -o -iname \*.hpp \) | xargs clang-format -style="{ColumnLimit : 100}" -i 3 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char *argv[]) { 5 | 6 | argparse::ArgumentParser program("lc"); 7 | program.add_argument("FILE") 8 | .action([](const std::string& value) { return value; }); 9 | 10 | try { 11 | program.parse_args(argc, argv); 12 | } 13 | catch (const std::runtime_error& err) { 14 | std::cout << err.what() << std::endl; 15 | std::cout << program; 16 | exit(0); 17 | } 18 | 19 | auto path = program.get("FILE"); 20 | std::cout << lc::count_lines(path) << " " << path << "\n"; 21 | } 22 | -------------------------------------------------------------------------------- /include/lc/count_lines.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace lc { 9 | 10 | namespace detail { 11 | struct LineCounter { 12 | std::error_code error; 13 | mio::mmap_source file_mmap; 14 | std::size_t file_size; 15 | std::size_t num_chunks; 16 | std::size_t size_per_chunk; 17 | std::atomic_size_t result{0}; 18 | std::vector threads; 19 | 20 | // Thread function - for each chunk 21 | void count_lines_in_chunk(std::size_t chunk_start, std::size_t chunk_end); 22 | 23 | // Constructor 24 | // Memory map the file 25 | LineCounter(const std::string& path); 26 | 27 | // Main function that counts the lines 28 | // Spawns threads (N = num_chunks) 29 | std::size_t count(); 30 | }; 31 | } // namespace detail 32 | 33 | std::size_t count_lines(const std::string& path); 34 | 35 | } // namespace lc 36 | -------------------------------------------------------------------------------- /utils/make_large_file.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | 4 | 5 | def get_file_size(size_in_units, unit): 6 | if unit == 'MB': 7 | file_size_in_bytes = size_in_units * 1024 * 1024 8 | elif unit == 'GB': 9 | file_size_in_bytes = size_in_units * 1024 * 1024 * 1024 10 | 11 | return file_size_in_bytes 12 | 13 | 14 | if __name__ == '__main__': 15 | parser = argparse.ArgumentParser(description='Make large files of arbitrary size to test lc') 16 | parser.add_argument('-s', '--size', help='size of file in Gigabytes', type=int, default=1) 17 | parser.add_argument('-m', '--megabytes', help='specify the size in Megabytes', action='store_true') 18 | args = parser.parse_args() 19 | 20 | if args.megabytes: 21 | unit = 'MB' 22 | else: 23 | unit = 'GB' 24 | 25 | size = get_file_size(args.size, unit) 26 | file_name = f'large_file_{args.size}{unit}' 27 | 28 | with open(file_name, 'wb') as fout: 29 | fout.write(os.urandom(size)) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | `lc` is a fast multi-threaded line counter. 6 | 7 | ## Getting Started 8 | 9 | Build and install `lc` 10 | 11 | ```bash 12 | mkdir build 13 | cd build 14 | cmake .. 15 | make 16 | sudo make install 17 | ``` 18 | 19 | Basic usage: 20 | 21 | ```console 22 | $ lc some_large_file.csv 23 | 20973777 some_large_file.csv 24 | ``` 25 | 26 | ## Performance Benchmark 27 | 28 | * This performance benchmark was run on an [NVIDIA DGX-1](https://docs.nvidia.com/dgx/dgx1-user-guide/introduction-to-dgx1.html#topic_hardware-specs) system. 29 | * Use `utils/make_large_file.py` to create files of desired size. 30 | * Benchmark using the [hyperfine](https://github.com/sharkdp/hyperfine) command-line benchmarking tool. 31 | * For large files, this line counter can be 2-10 times faster than `wc -l` depending on compute resources. 32 | 33 | | File size | Lines | `wc -l ` | `lc ` | 34 | |----------:|----------:|---------------:|--------------:| 35 | | 500 MB | 2047277 | 116.8 ms | 34.5 ms | 36 | | 1 GB | 4192675 | 243.0 ms | 60.9 ms | 37 | | 5 GB | 20969987 | 1.160 s | 200.5 ms | 38 | | 25 GB | 104839424 | 6.162 s | 738.8 ms | 39 | | 51 GB | 209706969 | 13.117 s | 1.461 s | 40 | | 100 GB | 209706969 | 27.279 s | 3.597 s | 41 | -------------------------------------------------------------------------------- /src/count_lines.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace lc { 4 | 5 | namespace detail { 6 | 7 | // Thread function - for each chunk 8 | void LineCounter::count_lines_in_chunk(std::size_t chunk_start, std::size_t chunk_end) { 9 | std::size_t lines_in_chunk = 0; 10 | 11 | std::size_t index; 12 | for (index = chunk_start; index < chunk_end; ++index) { 13 | if (file_mmap[index] == '\n') { 14 | lines_in_chunk += 1; 15 | } 16 | } 17 | result += lines_in_chunk; 18 | } 19 | 20 | LineCounter::LineCounter(const std::string& path) { 21 | file_mmap = mio::make_mmap_source(path, error); 22 | file_size = file_mmap.size(); 23 | num_chunks = std::thread::hardware_concurrency(); 24 | size_per_chunk = file_size / num_chunks; 25 | } 26 | 27 | std::size_t LineCounter::count() { 28 | if (error) { 29 | return result; 30 | } 31 | 32 | // schedule line counter for each chunk 33 | for (std::size_t i = 0; i < num_chunks; ++i) { 34 | std::size_t chunk_start = i * size_per_chunk; 35 | std::size_t chunk_end = chunk_start + size_per_chunk; 36 | 37 | if (chunk_end > file_size || (i == num_chunks - 1)) { 38 | chunk_end = file_size; 39 | 40 | // check if last character is not a newline 41 | if (file_mmap[chunk_end - 1] != '\n') { 42 | result += 1; 43 | } 44 | } 45 | 46 | threads.push_back( 47 | std::thread(std::bind(&LineCounter::count_lines_in_chunk, this, chunk_start, chunk_end))); 48 | } 49 | 50 | for (auto &t : threads) { 51 | t.join(); 52 | } 53 | 54 | return result; 55 | } 56 | 57 | } // namespace detail 58 | 59 | std::size_t count_lines(const std::string& path) { 60 | auto counter = detail::LineCounter(path); 61 | return counter.count(); 62 | } 63 | 64 | } // namespace lc 65 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | project(lc) 3 | 4 | if(NOT CMAKE_BUILD_TYPE) 5 | set(CMAKE_BUILD_TYPE Release) 6 | endif() 7 | 8 | set(CMAKE_CXX_FLAGS "-Wall -Wextra") 9 | set(CMAKE_CXX_FLAGS_DEBUG "-g") 10 | set(CMAKE_CXX_FLAGS_RELEASE "-O3") 11 | 12 | ############################################################################### 13 | ## file globbing ############################################################## 14 | ############################################################################### 15 | file(GLOB_RECURSE sources src/count_lines.cpp src/main.cpp) 16 | 17 | ############################################################################### 18 | ## target definitions ######################################################### 19 | ############################################################################### 20 | 21 | add_executable(lc ${sources}) 22 | target_compile_options(lc PUBLIC -std=c++17 -Wall) 23 | target_include_directories(lc PUBLIC include) 24 | 25 | ############################################################################### 26 | ## dependencies ############################################################### 27 | ############################################################################### 28 | 29 | find_package(Threads REQUIRED) 30 | target_link_libraries(lc PRIVATE Threads::Threads) 31 | 32 | ############################################################################### 33 | ## packaging ################################################################## 34 | ############################################################################### 35 | 36 | install(TARGETS lc) 37 | 38 | # now comes everything we need, to create a package 39 | # there are a lot more variables you can set, and some 40 | # you need to set for some package types, but we want to 41 | # be minimal here 42 | set(CPACK_PACKAGE_NAME "lc") 43 | set(CPACK_PACKAGE_VERSION "1.0.0") 44 | 45 | # we don't want to split our program up into several things 46 | set(CPACK_MONOLITHIC_INSTALL 1) 47 | 48 | # This must be last 49 | include(CPack) 50 | -------------------------------------------------------------------------------- /include/lc/argparse.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | __ _ _ __ __ _ _ __ __ _ _ __ ___ ___ 3 | / _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++ 4 | | (_| | | | (_| | |_) | (_| | | \__ \ __/ http://github.com/p-ranav/argparse 5 | \__,_|_| \__, | .__/ \__,_|_| |___/\___| 6 | |___/|_| 7 | 8 | Licensed under the MIT License . 9 | SPDX-License-Identifier: MIT 10 | Copyright (c) 2019-2021 Pranav Srinivas Kumar 11 | and other contributors. 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | */ 31 | #pragma once 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | namespace argparse { 54 | 55 | namespace details { // namespace for helper methods 56 | 57 | template 58 | struct is_container : std::false_type {}; 59 | 60 | template <> struct is_container : std::false_type {}; 61 | 62 | template 63 | struct is_container().begin()), 65 | decltype(std::declval().end()), 66 | decltype(std::declval().size())>> 67 | : std::true_type {}; 68 | 69 | template 70 | static constexpr bool is_container_v = is_container::value; 71 | 72 | template 73 | struct is_streamable : std::false_type {}; 74 | 75 | template 76 | struct is_streamable< 77 | T, std::void_t() << std::declval())>> 78 | : std::true_type {}; 79 | 80 | template 81 | static constexpr bool is_streamable_v = is_streamable::value; 82 | 83 | template 84 | static constexpr bool is_representable_v = 85 | is_streamable_v || is_container_v; 86 | 87 | constexpr size_t repr_max_container_size = 5; 88 | 89 | template std::string repr(T const &val) { 90 | if constexpr (std::is_same_v) { 91 | return val ? "true" : "false"; 92 | } else if constexpr (std::is_convertible_v) { 93 | return '"' + std::string{std::string_view{val}} + '"'; 94 | } else if constexpr (is_container_v) { 95 | std::stringstream out; 96 | out << "{"; 97 | const auto size = val.size(); 98 | if (size > 1) { 99 | out << repr(*val.begin()); 100 | std::for_each( 101 | std::next(val.begin()), 102 | std::next(val.begin(), std::min(size, repr_max_container_size) - 1), 103 | [&out](const auto &v) { out << " " << repr(v); }); 104 | if (size <= repr_max_container_size) 105 | out << " "; 106 | else 107 | out << "..."; 108 | } 109 | if (size > 0) 110 | out << repr(*std::prev(val.end())); 111 | out << "}"; 112 | return out.str(); 113 | } else if constexpr (is_streamable_v) { 114 | std::stringstream out; 115 | out << val; 116 | return out.str(); 117 | } else { 118 | return ""; 119 | } 120 | } 121 | 122 | namespace { 123 | 124 | template constexpr bool standard_signed_integer = false; 125 | template <> constexpr bool standard_signed_integer = true; 126 | template <> constexpr bool standard_signed_integer = true; 127 | template <> constexpr bool standard_signed_integer = true; 128 | template <> constexpr bool standard_signed_integer = true; 129 | template <> constexpr bool standard_signed_integer = true; 130 | 131 | template constexpr bool standard_unsigned_integer = false; 132 | template <> constexpr bool standard_unsigned_integer = true; 133 | template <> constexpr bool standard_unsigned_integer = true; 134 | template <> constexpr bool standard_unsigned_integer = true; 135 | template <> constexpr bool standard_unsigned_integer = true; 136 | template <> 137 | constexpr bool standard_unsigned_integer = true; 138 | 139 | } // namespace 140 | 141 | template 142 | constexpr bool standard_integer = 143 | standard_signed_integer || standard_unsigned_integer; 144 | 145 | template 146 | constexpr decltype(auto) apply_plus_one_impl(F &&f, Tuple &&t, Extra &&x, 147 | std::index_sequence) { 148 | return std::invoke(std::forward(f), std::get(std::forward(t))..., 149 | std::forward(x)); 150 | } 151 | 152 | template 153 | constexpr decltype(auto) apply_plus_one(F &&f, Tuple &&t, Extra &&x) { 154 | return details::apply_plus_one_impl( 155 | std::forward(f), std::forward(t), std::forward(x), 156 | std::make_index_sequence< 157 | std::tuple_size_v>>{}); 158 | } 159 | 160 | constexpr auto pointer_range(std::string_view s) noexcept { 161 | return std::tuple(s.data(), s.data() + s.size()); 162 | } 163 | 164 | template 165 | constexpr bool starts_with(std::basic_string_view prefix, 166 | std::basic_string_view s) noexcept { 167 | return s.substr(0, prefix.size()) == prefix; 168 | } 169 | 170 | enum class chars_format { 171 | scientific = 0x1, 172 | fixed = 0x2, 173 | hex = 0x4, 174 | general = fixed | scientific 175 | }; 176 | 177 | struct consume_hex_prefix_result { 178 | bool is_hexadecimal; 179 | std::string_view rest; 180 | }; 181 | 182 | using namespace std::literals; 183 | 184 | constexpr auto consume_hex_prefix(std::string_view s) 185 | -> consume_hex_prefix_result { 186 | if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { 187 | s.remove_prefix(2); 188 | return {true, s}; 189 | } else { 190 | return {false, s}; 191 | } 192 | } 193 | 194 | template 195 | inline auto do_from_chars(std::string_view s) -> T { 196 | T x; 197 | auto [first, last] = pointer_range(s); 198 | auto [ptr, ec] = std::from_chars(first, last, x, Param); 199 | if (ec == std::errc()) { 200 | if (ptr == last) 201 | return x; 202 | else 203 | throw std::invalid_argument{"pattern does not match to the end"}; 204 | } else if (ec == std::errc::invalid_argument) { 205 | throw std::invalid_argument{"pattern not found"}; 206 | } else if (ec == std::errc::result_out_of_range) { 207 | throw std::range_error{"not representable"}; 208 | } else { 209 | return x; // unreachable 210 | } 211 | } 212 | 213 | template struct parse_number { 214 | auto operator()(std::string_view s) -> T { 215 | return do_from_chars(s); 216 | } 217 | }; 218 | 219 | template struct parse_number { 220 | auto operator()(std::string_view s) -> T { 221 | if (auto [ok, rest] = consume_hex_prefix(s); ok) 222 | return do_from_chars(rest); 223 | else 224 | throw std::invalid_argument{"pattern not found"}; 225 | } 226 | }; 227 | 228 | template struct parse_number { 229 | auto operator()(std::string_view s) -> T { 230 | if (auto [ok, rest] = consume_hex_prefix(s); ok) 231 | return do_from_chars(rest); 232 | else if (starts_with("0"sv, s)) 233 | return do_from_chars(rest); 234 | else 235 | return do_from_chars(rest); 236 | } 237 | }; 238 | 239 | namespace { 240 | 241 | template constexpr auto generic_strtod = nullptr; 242 | template <> constexpr auto generic_strtod = strtof; 243 | template <> constexpr auto generic_strtod = strtod; 244 | template <> constexpr auto generic_strtod = strtold; 245 | 246 | } // namespace 247 | 248 | template inline auto do_strtod(std::string const &s) -> T { 249 | if (isspace(static_cast(s[0])) || s[0] == '+') 250 | throw std::invalid_argument{"pattern not found"}; 251 | 252 | auto [first, last] = pointer_range(s); 253 | char *ptr; 254 | 255 | errno = 0; 256 | if (auto x = generic_strtod(first, &ptr); errno == 0) { 257 | if (ptr == last) 258 | return x; 259 | else 260 | throw std::invalid_argument{"pattern does not match to the end"}; 261 | } else if (errno == ERANGE) { 262 | throw std::range_error{"not representable"}; 263 | } else { 264 | return x; // unreachable 265 | } 266 | } 267 | 268 | template struct parse_number { 269 | auto operator()(std::string const &s) -> T { 270 | if (auto r = consume_hex_prefix(s); r.is_hexadecimal) 271 | throw std::invalid_argument{ 272 | "chars_format::general does not parse hexfloat"}; 273 | 274 | return do_strtod(s); 275 | } 276 | }; 277 | 278 | template struct parse_number { 279 | auto operator()(std::string const &s) -> T { 280 | if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) 281 | throw std::invalid_argument{"chars_format::hex parses hexfloat"}; 282 | 283 | return do_strtod(s); 284 | } 285 | }; 286 | 287 | template struct parse_number { 288 | auto operator()(std::string const &s) -> T { 289 | if (auto r = consume_hex_prefix(s); r.is_hexadecimal) 290 | throw std::invalid_argument{ 291 | "chars_format::scientific does not parse hexfloat"}; 292 | if (s.find_first_of("eE") == s.npos) 293 | throw std::invalid_argument{ 294 | "chars_format::scientific requires exponent part"}; 295 | 296 | return do_strtod(s); 297 | } 298 | }; 299 | 300 | template struct parse_number { 301 | auto operator()(std::string const &s) -> T { 302 | if (auto r = consume_hex_prefix(s); r.is_hexadecimal) 303 | throw std::invalid_argument{ 304 | "chars_format::fixed does not parse hexfloat"}; 305 | if (s.find_first_of("eE") != s.npos) 306 | throw std::invalid_argument{ 307 | "chars_format::fixed does not parse exponent part"}; 308 | 309 | return do_strtod(s); 310 | } 311 | }; 312 | 313 | } // namespace details 314 | 315 | class ArgumentParser; 316 | 317 | class Argument { 318 | friend class ArgumentParser; 319 | friend auto operator<<(std::ostream &, ArgumentParser const &) 320 | -> std::ostream &; 321 | 322 | template 323 | explicit Argument(std::string_view(&&a)[N], std::index_sequence) 324 | : mIsOptional((is_optional(a[I]) || ...)), mIsRequired(false), 325 | mIsRepeatable(false), mIsUsed(false) { 326 | ((void)mNames.emplace_back(a[I]), ...); 327 | std::sort( 328 | mNames.begin(), mNames.end(), [](const auto &lhs, const auto &rhs) { 329 | return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size(); 330 | }); 331 | } 332 | 333 | public: 334 | template 335 | explicit Argument(std::string_view(&&a)[N]) 336 | : Argument(std::move(a), std::make_index_sequence{}) {} 337 | 338 | Argument &help(std::string aHelp) { 339 | mHelp = std::move(aHelp); 340 | return *this; 341 | } 342 | 343 | template Argument &default_value(T &&aDefaultValue) { 344 | mDefaultValueRepr = details::repr(aDefaultValue); 345 | mDefaultValue = std::forward(aDefaultValue); 346 | return *this; 347 | } 348 | 349 | Argument &required() { 350 | mIsRequired = true; 351 | return *this; 352 | } 353 | 354 | Argument &implicit_value(std::any aImplicitValue) { 355 | mImplicitValue = std::move(aImplicitValue); 356 | mNumArgs = 0; 357 | return *this; 358 | } 359 | 360 | template 361 | auto action(F &&aAction, Args &&... aBound) 362 | -> std::enable_if_t, 363 | Argument &> { 364 | using action_type = std::conditional_t< 365 | std::is_void_v>, 366 | void_action, valued_action>; 367 | if constexpr (sizeof...(Args) == 0) 368 | mAction.emplace(std::forward(aAction)); 369 | else 370 | mAction.emplace( 371 | [f = std::forward(aAction), 372 | tup = std::make_tuple(std::forward(aBound)...)]( 373 | std::string const &opt) mutable { 374 | return details::apply_plus_one(f, tup, opt); 375 | }); 376 | return *this; 377 | } 378 | 379 | auto &append() { 380 | mIsRepeatable = true; 381 | return *this; 382 | } 383 | 384 | template 385 | auto scan() -> std::enable_if_t, Argument &> { 386 | static_assert(!(std::is_const_v || std::is_volatile_v), 387 | "T should not be cv-qualified"); 388 | auto is_one_of = [](char c, auto... x) constexpr { 389 | return ((c == x) || ...); 390 | }; 391 | 392 | if constexpr (is_one_of(Shape, 'd') && details::standard_integer) 393 | action(details::parse_number()); 394 | else if constexpr (is_one_of(Shape, 'i') && details::standard_integer) 395 | action(details::parse_number()); 396 | else if constexpr (is_one_of(Shape, 'u') && 397 | details::standard_unsigned_integer) 398 | action(details::parse_number()); 399 | else if constexpr (is_one_of(Shape, 'o') && 400 | details::standard_unsigned_integer) 401 | action(details::parse_number()); 402 | else if constexpr (is_one_of(Shape, 'x', 'X') && 403 | details::standard_unsigned_integer) 404 | action(details::parse_number()); 405 | else if constexpr (is_one_of(Shape, 'a', 'A') && 406 | std::is_floating_point_v) 407 | action(details::parse_number()); 408 | else if constexpr (is_one_of(Shape, 'e', 'E') && 409 | std::is_floating_point_v) 410 | action(details::parse_number()); 411 | else if constexpr (is_one_of(Shape, 'f', 'F') && 412 | std::is_floating_point_v) 413 | action(details::parse_number()); 414 | else if constexpr (is_one_of(Shape, 'g', 'G') && 415 | std::is_floating_point_v) 416 | action(details::parse_number()); 417 | else 418 | static_assert(alignof(T) == 0, "No scan specification for T"); 419 | 420 | return *this; 421 | } 422 | 423 | Argument &nargs(int aNumArgs) { 424 | if (aNumArgs < 0) 425 | throw std::logic_error("Number of arguments must be non-negative"); 426 | mNumArgs = aNumArgs; 427 | return *this; 428 | } 429 | 430 | Argument &remaining() { 431 | mNumArgs = -1; 432 | return *this; 433 | } 434 | 435 | template 436 | Iterator consume(Iterator start, Iterator end, 437 | std::string_view usedName = {}) { 438 | if (!mIsRepeatable && mIsUsed) { 439 | throw std::runtime_error("Duplicate argument"); 440 | } 441 | mIsUsed = true; 442 | mUsedName = usedName; 443 | if (mNumArgs == 0) { 444 | mValues.emplace_back(mImplicitValue); 445 | return start; 446 | } else if (mNumArgs <= std::distance(start, end)) { 447 | if (auto expected = maybe_nargs()) { 448 | end = std::next(start, *expected); 449 | if (std::any_of(start, end, Argument::is_optional)) { 450 | throw std::runtime_error("optional argument in parameter sequence"); 451 | } 452 | } 453 | 454 | struct action_apply { 455 | void operator()(valued_action &f) { 456 | std::transform(start, end, std::back_inserter(self.mValues), f); 457 | } 458 | 459 | void operator()(void_action &f) { 460 | std::for_each(start, end, f); 461 | if (!self.mDefaultValue.has_value()) { 462 | if (auto expected = self.maybe_nargs()) 463 | self.mValues.resize(*expected); 464 | } 465 | } 466 | 467 | Iterator start, end; 468 | Argument &self; 469 | }; 470 | std::visit(action_apply{start, end, *this}, mAction); 471 | return end; 472 | } else if (mDefaultValue.has_value()) { 473 | return start; 474 | } else { 475 | throw std::runtime_error("Too few arguments"); 476 | } 477 | } 478 | 479 | /* 480 | * @throws std::runtime_error if argument values are not valid 481 | */ 482 | void validate() const { 483 | if (auto expected = maybe_nargs()) { 484 | if (mIsOptional) { 485 | if (mIsUsed && mValues.size() != *expected && !mIsRepeatable && 486 | !mDefaultValue.has_value()) { 487 | std::stringstream stream; 488 | stream << mUsedName << ": expected " << *expected << " argument(s). " 489 | << mValues.size() << " provided."; 490 | throw std::runtime_error(stream.str()); 491 | } else { 492 | // TODO: check if an implicit value was programmed for this argument 493 | if (!mIsUsed && !mDefaultValue.has_value() && mIsRequired) { 494 | std::stringstream stream; 495 | stream << mNames[0] << ": required."; 496 | throw std::runtime_error(stream.str()); 497 | } 498 | if (mIsUsed && mIsRequired && mValues.size() == 0) { 499 | std::stringstream stream; 500 | stream << mUsedName << ": no value provided."; 501 | throw std::runtime_error(stream.str()); 502 | } 503 | } 504 | } else { 505 | if (mValues.size() != expected && !mDefaultValue.has_value()) { 506 | std::stringstream stream; 507 | if (!mUsedName.empty()) 508 | stream << mUsedName << ": "; 509 | stream << *expected << " argument(s) expected. " << mValues.size() 510 | << " provided."; 511 | throw std::runtime_error(stream.str()); 512 | } 513 | } 514 | } 515 | } 516 | 517 | auto maybe_nargs() const -> std::optional { 518 | if (mNumArgs < 0) 519 | return std::nullopt; 520 | else 521 | return static_cast(mNumArgs); 522 | } 523 | 524 | size_t get_arguments_length() const { 525 | return std::accumulate(std::begin(mNames), std::end(mNames), size_t(0), 526 | [](const auto &sum, const auto &s) { 527 | return sum + s.size() + 528 | 1; // +1 for space between names 529 | }); 530 | } 531 | 532 | friend std::ostream &operator<<(std::ostream &stream, 533 | const Argument &argument) { 534 | std::stringstream nameStream; 535 | std::copy(std::begin(argument.mNames), std::end(argument.mNames), 536 | std::ostream_iterator(nameStream, " ")); 537 | stream << nameStream.str() << "\t" << argument.mHelp; 538 | if (argument.mDefaultValue.has_value()) { 539 | if (!argument.mHelp.empty()) 540 | stream << " "; 541 | stream << "[default: " << argument.mDefaultValueRepr << "]"; 542 | } else if (argument.mIsRequired) { 543 | if (!argument.mHelp.empty()) 544 | stream << " "; 545 | stream << "[required]"; 546 | } 547 | stream << "\n"; 548 | return stream; 549 | } 550 | 551 | template bool operator!=(const T &aRhs) const { 552 | return !(*this == aRhs); 553 | } 554 | 555 | /* 556 | * Compare to an argument value of known type 557 | * @throws std::logic_error in case of incompatible types 558 | */ 559 | template bool operator==(const T &aRhs) const { 560 | if constexpr (!details::is_container_v) { 561 | return get() == aRhs; 562 | } else { 563 | using ValueType = typename T::value_type; 564 | auto tLhs = get(); 565 | return std::equal(std::begin(tLhs), std::end(tLhs), std::begin(aRhs), 566 | std::end(aRhs), [](const auto &lhs, const auto &rhs) { 567 | return std::any_cast(lhs) == rhs; 568 | }); 569 | } 570 | } 571 | 572 | private: 573 | static constexpr int eof = std::char_traits::eof(); 574 | 575 | static auto lookahead(std::string_view s) -> int { 576 | if (s.empty()) 577 | return eof; 578 | else 579 | return static_cast(static_cast(s[0])); 580 | } 581 | 582 | /* 583 | * decimal-literal: 584 | * '0' 585 | * nonzero-digit digit-sequence_opt 586 | * integer-part fractional-part 587 | * fractional-part 588 | * integer-part '.' exponent-part_opt 589 | * integer-part exponent-part 590 | * 591 | * integer-part: 592 | * digit-sequence 593 | * 594 | * fractional-part: 595 | * '.' post-decimal-point 596 | * 597 | * post-decimal-point: 598 | * digit-sequence exponent-part_opt 599 | * 600 | * exponent-part: 601 | * 'e' post-e 602 | * 'E' post-e 603 | * 604 | * post-e: 605 | * sign_opt digit-sequence 606 | * 607 | * sign: one of 608 | * '+' '-' 609 | */ 610 | static bool is_decimal_literal(std::string_view s) { 611 | auto is_digit = [](auto c) constexpr { 612 | switch (c) { 613 | case '0': 614 | case '1': 615 | case '2': 616 | case '3': 617 | case '4': 618 | case '5': 619 | case '6': 620 | case '7': 621 | case '8': 622 | case '9': 623 | return true; 624 | default: 625 | return false; 626 | } 627 | }; 628 | 629 | // precondition: we have consumed or will consume at least one digit 630 | auto consume_digits = [=](std::string_view s) { 631 | auto it = std::find_if_not(std::begin(s), std::end(s), is_digit); 632 | return s.substr(it - std::begin(s)); 633 | }; 634 | 635 | switch (lookahead(s)) { 636 | case '0': { 637 | s.remove_prefix(1); 638 | if (s.empty()) 639 | return true; 640 | else 641 | goto integer_part; 642 | } 643 | case '1': 644 | case '2': 645 | case '3': 646 | case '4': 647 | case '5': 648 | case '6': 649 | case '7': 650 | case '8': 651 | case '9': { 652 | s = consume_digits(s); 653 | if (s.empty()) 654 | return true; 655 | else 656 | goto integer_part_consumed; 657 | } 658 | case '.': { 659 | s.remove_prefix(1); 660 | goto post_decimal_point; 661 | } 662 | default: 663 | return false; 664 | } 665 | 666 | integer_part: 667 | s = consume_digits(s); 668 | integer_part_consumed: 669 | switch (lookahead(s)) { 670 | case '.': { 671 | s.remove_prefix(1); 672 | if (is_digit(lookahead(s))) 673 | goto post_decimal_point; 674 | else 675 | goto exponent_part_opt; 676 | } 677 | case 'e': 678 | case 'E': { 679 | s.remove_prefix(1); 680 | goto post_e; 681 | } 682 | default: 683 | return false; 684 | } 685 | 686 | post_decimal_point: 687 | if (is_digit(lookahead(s))) { 688 | s = consume_digits(s); 689 | goto exponent_part_opt; 690 | } else { 691 | return false; 692 | } 693 | 694 | exponent_part_opt: 695 | switch (lookahead(s)) { 696 | case eof: 697 | return true; 698 | case 'e': 699 | case 'E': { 700 | s.remove_prefix(1); 701 | goto post_e; 702 | } 703 | default: 704 | return false; 705 | } 706 | 707 | post_e: 708 | switch (lookahead(s)) { 709 | case '-': 710 | case '+': 711 | s.remove_prefix(1); 712 | } 713 | if (is_digit(lookahead(s))) { 714 | s = consume_digits(s); 715 | return s.empty(); 716 | } else { 717 | return false; 718 | } 719 | } 720 | 721 | static bool is_optional(std::string_view aName) { 722 | return !is_positional(aName); 723 | } 724 | 725 | /* 726 | * positional: 727 | * _empty_ 728 | * '-' 729 | * '-' decimal-literal 730 | * !'-' anything 731 | */ 732 | static bool is_positional(std::string_view aName) { 733 | switch (lookahead(aName)) { 734 | case eof: 735 | return true; 736 | case '-': { 737 | aName.remove_prefix(1); 738 | if (aName.empty()) 739 | return true; 740 | else 741 | return is_decimal_literal(aName); 742 | } 743 | default: 744 | return true; 745 | } 746 | } 747 | 748 | /* 749 | * Get argument value given a type 750 | * @throws std::logic_error in case of incompatible types 751 | */ 752 | template T get() const { 753 | if (!mValues.empty()) { 754 | if constexpr (details::is_container_v) 755 | return any_cast_container(mValues); 756 | else 757 | return std::any_cast(mValues.front()); 758 | } 759 | if (mDefaultValue.has_value()) { 760 | return std::any_cast(mDefaultValue); 761 | } 762 | throw std::logic_error("No value provided"); 763 | } 764 | 765 | /* 766 | * Get argument value given a type. 767 | * @pre The object has no default value. 768 | * @returns The stored value if any, std::nullopt otherwise. 769 | */ 770 | template auto present() const -> std::optional { 771 | if (mDefaultValue.has_value()) 772 | throw std::logic_error("Argument with default value always presents"); 773 | 774 | if (mValues.empty()) 775 | return std::nullopt; 776 | else if constexpr (details::is_container_v) 777 | return any_cast_container(mValues); 778 | else 779 | return std::any_cast(mValues.front()); 780 | } 781 | 782 | template 783 | static auto any_cast_container(const std::vector &aOperand) -> T { 784 | using ValueType = typename T::value_type; 785 | 786 | T tResult; 787 | std::transform( 788 | std::begin(aOperand), std::end(aOperand), std::back_inserter(tResult), 789 | [](const auto &value) { return std::any_cast(value); }); 790 | return tResult; 791 | } 792 | 793 | std::vector mNames; 794 | std::string_view mUsedName; 795 | std::string mHelp; 796 | std::any mDefaultValue; 797 | std::string mDefaultValueRepr; 798 | std::any mImplicitValue; 799 | using valued_action = std::function; 800 | using void_action = std::function; 801 | std::variant mAction{ 802 | std::in_place_type, 803 | [](const std::string &aValue) { return aValue; }}; 804 | std::vector mValues; 805 | int mNumArgs = 1; 806 | bool mIsOptional : true; 807 | bool mIsRequired : true; 808 | bool mIsRepeatable : true; 809 | bool mIsUsed : true; // True if the optional argument is used by user 810 | }; 811 | 812 | class ArgumentParser { 813 | public: 814 | explicit ArgumentParser(std::string aProgramName = {}, 815 | std::string aVersion = "1.0") 816 | : mProgramName(std::move(aProgramName)), mVersion(std::move(aVersion)) { 817 | add_argument("-h", "--help").help("shows help message and exits").nargs(0); 818 | #ifndef ARGPARSE_LONG_VERSION_ARG_ONLY 819 | add_argument("-v", "--version") 820 | #else 821 | add_argument("--version") 822 | #endif 823 | .help("prints version information and exits") 824 | .nargs(0); 825 | } 826 | 827 | ArgumentParser(ArgumentParser &&) noexcept = default; 828 | ArgumentParser &operator=(ArgumentParser &&) = default; 829 | 830 | ArgumentParser(const ArgumentParser &other) 831 | : mProgramName(other.mProgramName), 832 | mPositionalArguments(other.mPositionalArguments), 833 | mOptionalArguments(other.mOptionalArguments) { 834 | for (auto it = std::begin(mPositionalArguments); it != std::end(mPositionalArguments); 835 | ++it) 836 | index_argument(it); 837 | for (auto it = std::begin(mOptionalArguments); it != std::end(mOptionalArguments); 838 | ++it) 839 | index_argument(it); 840 | } 841 | 842 | ArgumentParser &operator=(const ArgumentParser &other) { 843 | auto tmp = other; 844 | std::swap(*this, tmp); 845 | return *this; 846 | } 847 | 848 | // Parameter packing 849 | // Call add_argument with variadic number of string arguments 850 | template Argument &add_argument(Targs... Fargs) { 851 | using array_of_sv = std::string_view[sizeof...(Targs)]; 852 | auto tArgument = mOptionalArguments.emplace(cend(mOptionalArguments), 853 | array_of_sv{Fargs...}); 854 | 855 | if (!tArgument->mIsOptional) 856 | mPositionalArguments.splice(cend(mPositionalArguments), 857 | mOptionalArguments, tArgument); 858 | 859 | index_argument(tArgument); 860 | return *tArgument; 861 | } 862 | 863 | // Parameter packed add_parents method 864 | // Accepts a variadic number of ArgumentParser objects 865 | template 866 | ArgumentParser &add_parents(const Targs &... Fargs) { 867 | for (const ArgumentParser &tParentParser : {std::ref(Fargs)...}) { 868 | for (auto &tArgument : tParentParser.mPositionalArguments) { 869 | auto it = 870 | mPositionalArguments.insert(cend(mPositionalArguments), tArgument); 871 | index_argument(it); 872 | } 873 | for (auto &tArgument : tParentParser.mOptionalArguments) { 874 | auto it = 875 | mOptionalArguments.insert(cend(mOptionalArguments), tArgument); 876 | index_argument(it); 877 | } 878 | } 879 | return *this; 880 | } 881 | 882 | ArgumentParser &add_description(std::string aDescription) { 883 | mDescription = std::move(aDescription); 884 | return *this; 885 | } 886 | 887 | ArgumentParser &add_epilog(std::string aEpilog) { 888 | mEpilog = std::move(aEpilog); 889 | return *this; 890 | } 891 | 892 | /* Call parse_args_internal - which does all the work 893 | * Then, validate the parsed arguments 894 | * This variant is used mainly for testing 895 | * @throws std::runtime_error in case of any invalid argument 896 | */ 897 | void parse_args(const std::vector &aArguments) { 898 | parse_args_internal(aArguments); 899 | parse_args_validate(); 900 | } 901 | 902 | /* Main entry point for parsing command-line arguments using this 903 | * ArgumentParser 904 | * @throws std::runtime_error in case of any invalid argument 905 | */ 906 | void parse_args(int argc, const char *const argv[]) { 907 | std::vector arguments; 908 | std::copy(argv, argv + argc, std::back_inserter(arguments)); 909 | parse_args(arguments); 910 | } 911 | 912 | /* Getter for options with default values. 913 | * @throws std::logic_error if there is no such option 914 | * @throws std::logic_error if the option has no value 915 | * @throws std::bad_any_cast if the option is not of type T 916 | */ 917 | template 918 | T get(std::string_view aArgumentName) const { 919 | return (*this)[aArgumentName].get(); 920 | } 921 | 922 | /* Getter for options without default values. 923 | * @pre The option has no default value. 924 | * @throws std::logic_error if there is no such option 925 | * @throws std::bad_any_cast if the option is not of type T 926 | */ 927 | template 928 | auto present(std::string_view aArgumentName) -> std::optional { 929 | return (*this)[aArgumentName].present(); 930 | } 931 | 932 | /* Getter that returns true for user-supplied options. Returns false if not 933 | * user-supplied, even with a default value. 934 | */ 935 | auto is_used(std::string_view aArgumentName) const { 936 | return (*this)[aArgumentName].mIsUsed; 937 | } 938 | 939 | /* Indexing operator. Return a reference to an Argument object 940 | * Used in conjuction with Argument.operator== e.g., parser["foo"] == true 941 | * @throws std::logic_error in case of an invalid argument name 942 | */ 943 | Argument &operator[](std::string_view aArgumentName) const { 944 | auto tIterator = mArgumentMap.find(aArgumentName); 945 | if (tIterator != mArgumentMap.end()) { 946 | return *(tIterator->second); 947 | } 948 | throw std::logic_error("No such argument"); 949 | } 950 | 951 | // Print help message 952 | friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) 953 | -> std::ostream & { 954 | if (auto sen = std::ostream::sentry(stream)) { 955 | stream.setf(std::ios_base::left); 956 | stream << "Usage: " << parser.mProgramName << " [options] "; 957 | size_t tLongestArgumentLength = parser.get_length_of_longest_argument(); 958 | 959 | for (const auto &argument : parser.mPositionalArguments) { 960 | stream << argument.mNames.front() << " "; 961 | } 962 | stream << "\n\n"; 963 | 964 | if (!parser.mDescription.empty()) 965 | stream << parser.mDescription << "\n\n"; 966 | 967 | if (!parser.mPositionalArguments.empty()) 968 | stream << "Positional arguments:\n"; 969 | 970 | for (const auto &mPositionalArgument : parser.mPositionalArguments) { 971 | stream.width(tLongestArgumentLength); 972 | stream << mPositionalArgument; 973 | } 974 | 975 | if (!parser.mOptionalArguments.empty()) 976 | stream << (parser.mPositionalArguments.empty() ? "" : "\n") 977 | << "Optional arguments:\n"; 978 | 979 | for (const auto &mOptionalArgument : parser.mOptionalArguments) { 980 | stream.width(tLongestArgumentLength); 981 | stream << mOptionalArgument; 982 | } 983 | 984 | if (!parser.mEpilog.empty()) 985 | stream << parser.mEpilog << "\n\n"; 986 | } 987 | 988 | return stream; 989 | } 990 | 991 | // Format help message 992 | auto help() const -> std::stringstream { 993 | std::stringstream out; 994 | out << *this; 995 | return out; 996 | } 997 | 998 | // Printing the one and only help message 999 | // I've stuck with a simple message format, nothing fancy. 1000 | [[deprecated("Use cout << program; instead. See also help().")]] std::string 1001 | print_help() { 1002 | auto out = help(); 1003 | std::cout << out.rdbuf(); 1004 | return out.str(); 1005 | } 1006 | 1007 | private: 1008 | /* 1009 | * @throws std::runtime_error in case of any invalid argument 1010 | */ 1011 | void parse_args_internal(const std::vector &aArguments) { 1012 | if (mProgramName.empty() && !aArguments.empty()) { 1013 | mProgramName = aArguments.front(); 1014 | } 1015 | auto end = std::end(aArguments); 1016 | auto positionalArgumentIt = std::begin(mPositionalArguments); 1017 | for (auto it = std::next(std::begin(aArguments)); it != end;) { 1018 | const auto &tCurrentArgument = *it; 1019 | if (Argument::is_positional(tCurrentArgument)) { 1020 | if (positionalArgumentIt == std::end(mPositionalArguments)) { 1021 | throw std::runtime_error( 1022 | "Maximum number of positional arguments exceeded"); 1023 | } 1024 | auto tArgument = positionalArgumentIt++; 1025 | it = tArgument->consume(it, end); 1026 | continue; 1027 | } 1028 | 1029 | auto tIterator = mArgumentMap.find(tCurrentArgument); 1030 | if (tIterator != mArgumentMap.end()) { 1031 | auto tArgument = tIterator->second; 1032 | 1033 | // the first optional argument is --help 1034 | if (tArgument == mOptionalArguments.begin()) { 1035 | std::cout << *this; 1036 | std::exit(0); 1037 | } 1038 | // the second optional argument is --version 1039 | else if (tArgument == std::next(mOptionalArguments.begin(), 1)) { 1040 | std::cout << mVersion << "\n"; 1041 | std::exit(0); 1042 | } 1043 | 1044 | it = tArgument->consume(std::next(it), end, tIterator->first); 1045 | } else if (const auto &tCompoundArgument = tCurrentArgument; 1046 | tCompoundArgument.size() > 1 && tCompoundArgument[0] == '-' && 1047 | tCompoundArgument[1] != '-') { 1048 | ++it; 1049 | for (size_t j = 1; j < tCompoundArgument.size(); j++) { 1050 | auto tHypotheticalArgument = std::string{'-', tCompoundArgument[j]}; 1051 | auto tIterator2 = mArgumentMap.find(tHypotheticalArgument); 1052 | if (tIterator2 != mArgumentMap.end()) { 1053 | auto tArgument = tIterator2->second; 1054 | it = tArgument->consume(it, end, tIterator2->first); 1055 | } else { 1056 | throw std::runtime_error("Unknown argument"); 1057 | } 1058 | } 1059 | } else { 1060 | throw std::runtime_error("Unknown argument"); 1061 | } 1062 | } 1063 | } 1064 | 1065 | /* 1066 | * @throws std::runtime_error in case of any invalid argument 1067 | */ 1068 | void parse_args_validate() { 1069 | // Check if all arguments are parsed 1070 | std::for_each(std::begin(mArgumentMap), std::end(mArgumentMap), 1071 | [](const auto &argPair) { 1072 | const auto &tArgument = argPair.second; 1073 | tArgument->validate(); 1074 | }); 1075 | } 1076 | 1077 | // Used by print_help. 1078 | size_t get_length_of_longest_argument() const { 1079 | if (mArgumentMap.empty()) 1080 | return 0; 1081 | std::vector argumentLengths(mArgumentMap.size()); 1082 | std::transform(std::begin(mArgumentMap), std::end(mArgumentMap), 1083 | std::begin(argumentLengths), [](const auto &argPair) { 1084 | const auto &tArgument = argPair.second; 1085 | return tArgument->get_arguments_length(); 1086 | }); 1087 | return *std::max_element(std::begin(argumentLengths), 1088 | std::end(argumentLengths)); 1089 | } 1090 | 1091 | using list_iterator = std::list::iterator; 1092 | 1093 | void index_argument(list_iterator argIt) { 1094 | for (auto &mName : std::as_const(argIt->mNames)) 1095 | mArgumentMap.insert_or_assign(mName, argIt); 1096 | } 1097 | 1098 | std::string mProgramName; 1099 | std::string mVersion; 1100 | std::string mDescription; 1101 | std::string mEpilog; 1102 | std::list mPositionalArguments; 1103 | std::list mOptionalArguments; 1104 | std::map> mArgumentMap; 1105 | }; 1106 | 1107 | } // namespace argparse 1108 | -------------------------------------------------------------------------------- /include/lc/mio.hpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 https://github.com/mandreyel 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | * software and associated documentation files (the "Software"), to deal in the Software 5 | * without restriction, including without limitation the rights to use, copy, modify, 6 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | * permit persons to whom the Software is furnished to do so, subject to the following 8 | * conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in all copies 11 | * or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 14 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 15 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 16 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 17 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 18 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | */ 20 | 21 | #ifndef MIO_MMAP_HEADER 22 | #define MIO_MMAP_HEADER 23 | 24 | // #include "mio/page.hpp" 25 | /* Copyright 2017 https://github.com/mandreyel 26 | * 27 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 28 | * software and associated documentation files (the "Software"), to deal in the Software 29 | * without restriction, including without limitation the rights to use, copy, modify, 30 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 31 | * permit persons to whom the Software is furnished to do so, subject to the following 32 | * conditions: 33 | * 34 | * The above copyright notice and this permission notice shall be included in all copies 35 | * or substantial portions of the Software. 36 | * 37 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 38 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 39 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 40 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 41 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 42 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 43 | */ 44 | 45 | #ifndef MIO_PAGE_HEADER 46 | #define MIO_PAGE_HEADER 47 | 48 | #ifdef _WIN32 49 | #include 50 | #else 51 | #include 52 | #endif 53 | 54 | namespace mio { 55 | 56 | /** 57 | * This is used by `basic_mmap` to determine whether to create a read-only or 58 | * a read-write memory mapping. 59 | */ 60 | enum class access_mode { read, write }; 61 | 62 | /** 63 | * Determines the operating system's page allocation granularity. 64 | * 65 | * On the first call to this function, it invokes the operating system specific syscall 66 | * to determine the page size, caches the value, and returns it. Any subsequent call to 67 | * this function serves the cached value, so no further syscalls are made. 68 | */ 69 | inline size_t page_size() { 70 | static const size_t page_size = [] { 71 | #ifdef _WIN32 72 | SYSTEM_INFO SystemInfo; 73 | GetSystemInfo(&SystemInfo); 74 | return SystemInfo.dwAllocationGranularity; 75 | #else 76 | return sysconf(_SC_PAGE_SIZE); 77 | #endif 78 | }(); 79 | return page_size; 80 | } 81 | 82 | /** 83 | * Alligns `offset` to the operating's system page size such that it subtracts the 84 | * difference until the nearest page boundary before `offset`, or does nothing if 85 | * `offset` is already page aligned. 86 | */ 87 | inline size_t make_offset_page_aligned(size_t offset) noexcept { 88 | const size_t page_size_ = page_size(); 89 | // Use integer division to round down to the nearest page alignment. 90 | return offset / page_size_ * page_size_; 91 | } 92 | 93 | } // namespace mio 94 | 95 | #endif // MIO_PAGE_HEADER 96 | 97 | #include 98 | #include 99 | #include 100 | #include 101 | 102 | #ifdef _WIN32 103 | #ifndef WIN32_LEAN_AND_MEAN 104 | #define WIN32_LEAN_AND_MEAN 105 | #endif // WIN32_LEAN_AND_MEAN 106 | #include 107 | #else // ifdef _WIN32 108 | #define INVALID_HANDLE_VALUE -1 109 | #endif // ifdef _WIN32 110 | 111 | namespace mio { 112 | 113 | // This value may be provided as the `length` parameter to the constructor or 114 | // `map`, in which case a memory mapping of the entire file is created. 115 | enum { map_entire_file = 0 }; 116 | 117 | #ifdef _WIN32 118 | using file_handle_type = HANDLE; 119 | #else 120 | using file_handle_type = int; 121 | #endif 122 | 123 | // This value represents an invalid file handle type. This can be used to 124 | // determine whether `basic_mmap::file_handle` is valid, for example. 125 | const static file_handle_type invalid_handle = INVALID_HANDLE_VALUE; 126 | 127 | template struct basic_mmap { 128 | using value_type = ByteT; 129 | using size_type = size_t; 130 | using reference = value_type &; 131 | using const_reference = const value_type &; 132 | using pointer = value_type *; 133 | using const_pointer = const value_type *; 134 | using difference_type = std::ptrdiff_t; 135 | using iterator = pointer; 136 | using const_iterator = const_pointer; 137 | using reverse_iterator = std::reverse_iterator; 138 | using const_reverse_iterator = std::reverse_iterator; 139 | using iterator_category = std::random_access_iterator_tag; 140 | using handle_type = file_handle_type; 141 | 142 | static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char."); 143 | 144 | private: 145 | // Points to the first requested byte, and not to the actual start of the mapping. 146 | pointer data_ = nullptr; 147 | 148 | // Length--in bytes--requested by user (which may not be the length of the 149 | // full mapping) and the length of the full mapping. 150 | size_type length_ = 0; 151 | size_type mapped_length_ = 0; 152 | 153 | // Letting user map a file using both an existing file handle and a path 154 | // introcudes some complexity (see `is_handle_internal_`). 155 | // On POSIX, we only need a file handle to create a mapping, while on 156 | // Windows systems the file handle is necessary to retrieve a file mapping 157 | // handle, but any subsequent operations on the mapped region must be done 158 | // through the latter. 159 | handle_type file_handle_ = INVALID_HANDLE_VALUE; 160 | #ifdef _WIN32 161 | handle_type file_mapping_handle_ = INVALID_HANDLE_VALUE; 162 | #endif 163 | 164 | // Letting user map a file using both an existing file handle and a path 165 | // introcudes some complexity in that we must not close the file handle if 166 | // user provided it, but we must close it if we obtained it using the 167 | // provided path. For this reason, this flag is used to determine when to 168 | // close `file_handle_`. 169 | bool is_handle_internal_; 170 | 171 | public: 172 | /** 173 | * The default constructed mmap object is in a non-mapped state, that is, 174 | * any operation that attempts to access nonexistent underlying data will 175 | * result in undefined behaviour/segmentation faults. 176 | */ 177 | basic_mmap() = default; 178 | 179 | #ifdef __cpp_exceptions 180 | /** 181 | * The same as invoking the `map` function, except any error that may occur 182 | * while establishing the mapping is wrapped in a `std::system_error` and is 183 | * thrown. 184 | */ 185 | template 186 | basic_mmap(const String &path, const size_type offset = 0, 187 | const size_type length = map_entire_file) { 188 | std::error_code error; 189 | map(path, offset, length, error); 190 | if (error) { 191 | throw std::system_error(error); 192 | } 193 | } 194 | 195 | /** 196 | * The same as invoking the `map` function, except any error that may occur 197 | * while establishing the mapping is wrapped in a `std::system_error` and is 198 | * thrown. 199 | */ 200 | basic_mmap(const handle_type handle, const size_type offset = 0, 201 | const size_type length = map_entire_file) { 202 | std::error_code error; 203 | map(handle, offset, length, error); 204 | if (error) { 205 | throw std::system_error(error); 206 | } 207 | } 208 | #endif // __cpp_exceptions 209 | 210 | /** 211 | * `basic_mmap` has single-ownership semantics, so transferring ownership 212 | * may only be accomplished by moving the object. 213 | */ 214 | basic_mmap(const basic_mmap &) = delete; 215 | basic_mmap(basic_mmap &&); 216 | basic_mmap &operator=(const basic_mmap &) = delete; 217 | basic_mmap &operator=(basic_mmap &&); 218 | 219 | /** 220 | * If this is a read-write mapping, the destructor invokes sync. Regardless 221 | * of the access mode, unmap is invoked as a final step. 222 | */ 223 | ~basic_mmap(); 224 | 225 | /** 226 | * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows, 227 | * however, a mapped region of a file gets its own handle, which is returned by 228 | * 'mapping_handle'. 229 | */ 230 | handle_type file_handle() const noexcept { return file_handle_; } 231 | handle_type mapping_handle() const noexcept; 232 | 233 | /** Returns whether a valid memory mapping has been created. */ 234 | bool is_open() const noexcept { return file_handle_ != invalid_handle; } 235 | 236 | /** 237 | * Returns true if no mapping was established, that is, conceptually the 238 | * same as though the length that was mapped was 0. This function is 239 | * provided so that this class has Container semantics. 240 | */ 241 | bool empty() const noexcept { return length() == 0; } 242 | 243 | /** Returns true if a mapping was established. */ 244 | bool is_mapped() const noexcept; 245 | 246 | /** 247 | * `size` and `length` both return the logical length, i.e. the number of bytes 248 | * user requested to be mapped, while `mapped_length` returns the actual number of 249 | * bytes that were mapped which is a multiple of the underlying operating system's 250 | * page allocation granularity. 251 | */ 252 | size_type size() const noexcept { return length(); } 253 | size_type length() const noexcept { return length_; } 254 | size_type mapped_length() const noexcept { return mapped_length_; } 255 | 256 | /** Returns the offset relative to the start of the mapping. */ 257 | size_type mapping_offset() const noexcept { return mapped_length_ - length_; } 258 | 259 | /** 260 | * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping 261 | * exists. 262 | */ 263 | template ::type> 265 | pointer data() noexcept { 266 | return data_; 267 | } 268 | const_pointer data() const noexcept { return data_; } 269 | 270 | /** 271 | * Returns an iterator to the first requested byte, if a valid memory mapping 272 | * exists, otherwise this function call is undefined behaviour. 273 | */ 274 | template ::type> 276 | iterator begin() noexcept { 277 | return data(); 278 | } 279 | const_iterator begin() const noexcept { return data(); } 280 | const_iterator cbegin() const noexcept { return data(); } 281 | 282 | /** 283 | * Returns an iterator one past the last requested byte, if a valid memory mapping 284 | * exists, otherwise this function call is undefined behaviour. 285 | */ 286 | template ::type> 288 | iterator end() noexcept { 289 | return data() + length(); 290 | } 291 | const_iterator end() const noexcept { return data() + length(); } 292 | const_iterator cend() const noexcept { return data() + length(); } 293 | 294 | /** 295 | * Returns a reverse iterator to the last memory mapped byte, if a valid 296 | * memory mapping exists, otherwise this function call is undefined 297 | * behaviour. 298 | */ 299 | template ::type> 301 | reverse_iterator rbegin() noexcept { 302 | return reverse_iterator(end()); 303 | } 304 | const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } 305 | const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(end()); } 306 | 307 | /** 308 | * Returns a reverse iterator past the first mapped byte, if a valid memory 309 | * mapping exists, otherwise this function call is undefined behaviour. 310 | */ 311 | template ::type> 313 | reverse_iterator rend() noexcept { 314 | return reverse_iterator(begin()); 315 | } 316 | const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } 317 | const_reverse_iterator crend() const noexcept { return const_reverse_iterator(begin()); } 318 | 319 | /** 320 | * Returns a reference to the `i`th byte from the first requested byte (as returned 321 | * by `data`). If this is invoked when no valid memory mapping has been created 322 | * prior to this call, undefined behaviour ensues. 323 | */ 324 | reference operator[](const size_type i) noexcept { return data_[i]; } 325 | const_reference operator[](const size_type i) const noexcept { return data_[i]; } 326 | 327 | /** 328 | * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the 329 | * reason is reported via `error` and the object remains in a state as if this 330 | * function hadn't been called. 331 | * 332 | * `path`, which must be a path to an existing file, is used to retrieve a file 333 | * handle (which is closed when the object destructs or `unmap` is called), which is 334 | * then used to memory map the requested region. Upon failure, `error` is set to 335 | * indicate the reason and the object remains in an unmapped state. 336 | * 337 | * `offset` is the number of bytes, relative to the start of the file, where the 338 | * mapping should begin. When specifying it, there is no need to worry about 339 | * providing a value that is aligned with the operating system's page allocation 340 | * granularity. This is adjusted by the implementation such that the first requested 341 | * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at 342 | * `offset` from the start of the file. 343 | * 344 | * `length` is the number of bytes to map. It may be `map_entire_file`, in which 345 | * case a mapping of the entire file is created. 346 | */ 347 | template 348 | void map(const String &path, const size_type offset, const size_type length, 349 | std::error_code &error); 350 | 351 | /** 352 | * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the 353 | * reason is reported via `error` and the object remains in a state as if this 354 | * function hadn't been called. 355 | * 356 | * `path`, which must be a path to an existing file, is used to retrieve a file 357 | * handle (which is closed when the object destructs or `unmap` is called), which is 358 | * then used to memory map the requested region. Upon failure, `error` is set to 359 | * indicate the reason and the object remains in an unmapped state. 360 | * 361 | * The entire file is mapped. 362 | */ 363 | template void map(const String &path, std::error_code &error) { 364 | map(path, 0, map_entire_file, error); 365 | } 366 | 367 | /** 368 | * Establishes a memory mapping with AccessMode. If the mapping is 369 | * unsuccesful, the reason is reported via `error` and the object remains in 370 | * a state as if this function hadn't been called. 371 | * 372 | * `handle`, which must be a valid file handle, which is used to memory map the 373 | * requested region. Upon failure, `error` is set to indicate the reason and the 374 | * object remains in an unmapped state. 375 | * 376 | * `offset` is the number of bytes, relative to the start of the file, where the 377 | * mapping should begin. When specifying it, there is no need to worry about 378 | * providing a value that is aligned with the operating system's page allocation 379 | * granularity. This is adjusted by the implementation such that the first requested 380 | * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at 381 | * `offset` from the start of the file. 382 | * 383 | * `length` is the number of bytes to map. It may be `map_entire_file`, in which 384 | * case a mapping of the entire file is created. 385 | */ 386 | void map(const handle_type handle, const size_type offset, const size_type length, 387 | std::error_code &error); 388 | 389 | /** 390 | * Establishes a memory mapping with AccessMode. If the mapping is 391 | * unsuccesful, the reason is reported via `error` and the object remains in 392 | * a state as if this function hadn't been called. 393 | * 394 | * `handle`, which must be a valid file handle, which is used to memory map the 395 | * requested region. Upon failure, `error` is set to indicate the reason and the 396 | * object remains in an unmapped state. 397 | * 398 | * The entire file is mapped. 399 | */ 400 | void map(const handle_type handle, std::error_code &error) { 401 | map(handle, 0, map_entire_file, error); 402 | } 403 | 404 | /** 405 | * If a valid memory mapping has been created prior to this call, this call 406 | * instructs the kernel to unmap the memory region and disassociate this object 407 | * from the file. 408 | * 409 | * The file handle associated with the file that is mapped is only closed if the 410 | * mapping was created using a file path. If, on the other hand, an existing 411 | * file handle was used to create the mapping, the file handle is not closed. 412 | */ 413 | void unmap(); 414 | 415 | void swap(basic_mmap &other); 416 | 417 | /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ 418 | template 419 | typename std::enable_if::type sync(std::error_code &error); 420 | 421 | /** 422 | * All operators compare the address of the first byte and size of the two mapped 423 | * regions. 424 | */ 425 | 426 | private: 427 | template ::type> 429 | pointer get_mapping_start() noexcept { 430 | return !data() ? nullptr : data() - mapping_offset(); 431 | } 432 | 433 | const_pointer get_mapping_start() const noexcept { 434 | return !data() ? nullptr : data() - mapping_offset(); 435 | } 436 | 437 | /** 438 | * The destructor syncs changes to disk if `AccessMode` is `write`, but not 439 | * if it's `read`, but since the destructor cannot be templated, we need to 440 | * do SFINAE in a dedicated function, where one syncs and the other is a noop. 441 | */ 442 | template 443 | typename std::enable_if::type conditional_sync(); 444 | template 445 | typename std::enable_if::type conditional_sync(); 446 | }; 447 | 448 | template 449 | bool operator==(const basic_mmap &a, const basic_mmap &b); 450 | 451 | template 452 | bool operator!=(const basic_mmap &a, const basic_mmap &b); 453 | 454 | template 455 | bool operator<(const basic_mmap &a, const basic_mmap &b); 456 | 457 | template 458 | bool operator<=(const basic_mmap &a, const basic_mmap &b); 459 | 460 | template 461 | bool operator>(const basic_mmap &a, const basic_mmap &b); 462 | 463 | template 464 | bool operator>=(const basic_mmap &a, const basic_mmap &b); 465 | 466 | /** 467 | * This is the basis for all read-only mmap objects and should be preferred over 468 | * directly using `basic_mmap`. 469 | */ 470 | template using basic_mmap_source = basic_mmap; 471 | 472 | /** 473 | * This is the basis for all read-write mmap objects and should be preferred over 474 | * directly using `basic_mmap`. 475 | */ 476 | template using basic_mmap_sink = basic_mmap; 477 | 478 | /** 479 | * These aliases cover the most common use cases, both representing a raw byte stream 480 | * (either with a char or an unsigned char/uint8_t). 481 | */ 482 | using mmap_source = basic_mmap_source; 483 | using ummap_source = basic_mmap_source; 484 | 485 | using mmap_sink = basic_mmap_sink; 486 | using ummap_sink = basic_mmap_sink; 487 | 488 | /** 489 | * Convenience factory method that constructs a mapping for any `basic_mmap` or 490 | * `basic_mmap` type. 491 | */ 492 | template 493 | MMap make_mmap(const MappingToken &token, int64_t offset, int64_t length, std::error_code &error) { 494 | MMap mmap; 495 | mmap.map(token, offset, length, error); 496 | return mmap; 497 | } 498 | 499 | /** 500 | * Convenience factory method. 501 | * 502 | * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`, 503 | * `std::filesystem::path`, `std::vector`, or similar), or a 504 | * `mmap_source::handle_type`. 505 | */ 506 | template 507 | mmap_source make_mmap_source(const MappingToken &token, mmap_source::size_type offset, 508 | mmap_source::size_type length, std::error_code &error) { 509 | return make_mmap(token, offset, length, error); 510 | } 511 | 512 | template 513 | mmap_source make_mmap_source(const MappingToken &token, std::error_code &error) { 514 | return make_mmap_source(token, 0, map_entire_file, error); 515 | } 516 | 517 | /** 518 | * Convenience factory method. 519 | * 520 | * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`, 521 | * `std::filesystem::path`, `std::vector`, or similar), or a 522 | * `mmap_sink::handle_type`. 523 | */ 524 | template 525 | mmap_sink make_mmap_sink(const MappingToken &token, mmap_sink::size_type offset, 526 | mmap_sink::size_type length, std::error_code &error) { 527 | return make_mmap(token, offset, length, error); 528 | } 529 | 530 | template 531 | mmap_sink make_mmap_sink(const MappingToken &token, std::error_code &error) { 532 | return make_mmap_sink(token, 0, map_entire_file, error); 533 | } 534 | 535 | } // namespace mio 536 | 537 | // #include "detail/mmap.ipp" 538 | /* Copyright 2017 https://github.com/mandreyel 539 | * 540 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 541 | * software and associated documentation files (the "Software"), to deal in the Software 542 | * without restriction, including without limitation the rights to use, copy, modify, 543 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 544 | * permit persons to whom the Software is furnished to do so, subject to the following 545 | * conditions: 546 | * 547 | * The above copyright notice and this permission notice shall be included in all copies 548 | * or substantial portions of the Software. 549 | * 550 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 551 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 552 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 553 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 554 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 555 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 556 | */ 557 | 558 | #ifndef MIO_BASIC_MMAP_IMPL 559 | #define MIO_BASIC_MMAP_IMPL 560 | 561 | // #include "mio/mmap.hpp" 562 | 563 | // #include "mio/page.hpp" 564 | 565 | // #include "mio/detail/string_util.hpp" 566 | /* Copyright 2017 https://github.com/mandreyel 567 | * 568 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 569 | * software and associated documentation files (the "Software"), to deal in the Software 570 | * without restriction, including without limitation the rights to use, copy, modify, 571 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 572 | * permit persons to whom the Software is furnished to do so, subject to the following 573 | * conditions: 574 | * 575 | * The above copyright notice and this permission notice shall be included in all copies 576 | * or substantial portions of the Software. 577 | * 578 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 579 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 580 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 581 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 582 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 583 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 584 | */ 585 | 586 | #ifndef MIO_STRING_UTIL_HEADER 587 | #define MIO_STRING_UTIL_HEADER 588 | 589 | #include 590 | 591 | namespace mio { 592 | namespace detail { 593 | 594 | template ::type, 595 | typename = decltype(std::declval().data()), 596 | typename = typename std::enable_if::value 597 | #ifdef _WIN32 598 | || std::is_same::value 599 | #endif 600 | >::type> 601 | struct char_type_helper { 602 | using type = typename C::value_type; 603 | }; 604 | 605 | template struct char_type { using type = typename char_type_helper::type; }; 606 | 607 | // TODO: can we avoid this brute force approach? 608 | template <> struct char_type { using type = char; }; 609 | 610 | template <> struct char_type { using type = char; }; 611 | 612 | template struct char_type { using type = char; }; 613 | 614 | template struct char_type { using type = char; }; 615 | 616 | #ifdef _WIN32 617 | template <> struct char_type { using type = wchar_t; }; 618 | 619 | template <> struct char_type { using type = wchar_t; }; 620 | 621 | template struct char_type { using type = wchar_t; }; 622 | 623 | template struct char_type { using type = wchar_t; }; 624 | #endif // _WIN32 625 | 626 | template struct is_c_str_helper { 627 | static constexpr bool value = 628 | std::is_same::type>::type>::type>::type>::value; 632 | }; 633 | 634 | template struct is_c_str { 635 | static constexpr bool value = is_c_str_helper::value; 636 | }; 637 | 638 | #ifdef _WIN32 639 | template struct is_c_wstr { 640 | static constexpr bool value = is_c_str_helper::value; 641 | }; 642 | #endif // _WIN32 643 | 644 | template struct is_c_str_or_c_wstr { 645 | static constexpr bool value = is_c_str::value 646 | #ifdef _WIN32 647 | || is_c_wstr::value 648 | #endif 649 | ; 650 | }; 651 | 652 | template ().data()), 653 | typename = typename std::enable_if::value>::type> 654 | const typename char_type::type *c_str(const String &path) { 655 | return path.data(); 656 | } 657 | 658 | template ().empty()), 659 | typename = typename std::enable_if::value>::type> 660 | bool empty(const String &path) { 661 | return path.empty(); 662 | } 663 | 664 | template ::value>::type> 666 | const typename char_type::type *c_str(String path) { 667 | return path; 668 | } 669 | 670 | template ::value>::type> 672 | bool empty(String path) { 673 | return !path || (*path == 0); 674 | } 675 | 676 | } // namespace detail 677 | } // namespace mio 678 | 679 | #endif // MIO_STRING_UTIL_HEADER 680 | 681 | #include 682 | 683 | #ifndef _WIN32 684 | #include 685 | #include 686 | #include 687 | #include 688 | #endif 689 | 690 | namespace mio { 691 | namespace detail { 692 | 693 | #ifdef _WIN32 694 | namespace win { 695 | 696 | /** Returns the 4 upper bytes of an 8-byte integer. */ 697 | inline DWORD int64_high(int64_t n) noexcept { return n >> 32; } 698 | 699 | /** Returns the 4 lower bytes of an 8-byte integer. */ 700 | inline DWORD int64_low(int64_t n) noexcept { return n & 0xffffffff; } 701 | 702 | std::wstring s_2_ws(const std::string &s) { 703 | if (s.empty()) 704 | return {}; 705 | const auto s_length = static_cast(s.length()); 706 | auto buf = std::vector(s_length); 707 | const auto wide_char_count = 708 | MultiByteToWideChar(CP_UTF8, 0, s.c_str(), s_length, buf.data(), s_length); 709 | return std::wstring(buf.data(), wide_char_count); 710 | } 711 | 712 | template ::type, char>::value>::type> 714 | file_handle_type open_file_helper(const String &path, const access_mode mode) { 715 | return ::CreateFileW( 716 | s_2_ws(path).c_str(), mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, 717 | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); 718 | } 719 | 720 | template 721 | typename std::enable_if::type, wchar_t>::value, 722 | file_handle_type>::type 723 | open_file_helper(const String &path, const access_mode mode) { 724 | return ::CreateFileW( 725 | c_str(path), mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, 726 | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); 727 | } 728 | 729 | } // namespace win 730 | #endif // _WIN32 731 | 732 | /** 733 | * Returns the last platform specific system error (errno on POSIX and 734 | * GetLastError on Win) as a `std::error_code`. 735 | */ 736 | inline std::error_code last_error() noexcept { 737 | std::error_code error; 738 | #ifdef _WIN32 739 | error.assign(GetLastError(), std::system_category()); 740 | #else 741 | error.assign(errno, std::system_category()); 742 | #endif 743 | return error; 744 | } 745 | 746 | template 747 | file_handle_type open_file(const String &path, const access_mode mode, std::error_code &error) { 748 | error.clear(); 749 | if (detail::empty(path)) { 750 | error = std::make_error_code(std::errc::invalid_argument); 751 | return invalid_handle; 752 | } 753 | #ifdef _WIN32 754 | const auto handle = win::open_file_helper(path, mode); 755 | #else // POSIX 756 | const auto handle = ::open(c_str(path), mode == access_mode::read ? O_RDONLY : O_RDWR); 757 | #endif 758 | if (handle == invalid_handle) { 759 | error = detail::last_error(); 760 | } 761 | return handle; 762 | } 763 | 764 | inline size_t query_file_size(file_handle_type handle, std::error_code &error) { 765 | error.clear(); 766 | #ifdef _WIN32 767 | LARGE_INTEGER file_size; 768 | if (::GetFileSizeEx(handle, &file_size) == 0) { 769 | error = detail::last_error(); 770 | return 0; 771 | } 772 | return static_cast(file_size.QuadPart); 773 | #else // POSIX 774 | struct stat sbuf; 775 | if (::fstat(handle, &sbuf) == -1) { 776 | error = detail::last_error(); 777 | return 0; 778 | } 779 | return sbuf.st_size; 780 | #endif 781 | } 782 | 783 | struct mmap_context { 784 | char *data; 785 | int64_t length; 786 | int64_t mapped_length; 787 | #ifdef _WIN32 788 | file_handle_type file_mapping_handle; 789 | #endif 790 | }; 791 | 792 | inline mmap_context memory_map(const file_handle_type file_handle, const int64_t offset, 793 | const int64_t length, const access_mode mode, 794 | std::error_code &error) { 795 | const int64_t aligned_offset = make_offset_page_aligned(offset); 796 | const int64_t length_to_map = offset - aligned_offset + length; 797 | #ifdef _WIN32 798 | const int64_t max_file_size = offset + length; 799 | const auto file_mapping_handle = ::CreateFileMapping( 800 | file_handle, 0, mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, 801 | win::int64_high(max_file_size), win::int64_low(max_file_size), 0); 802 | if (file_mapping_handle == invalid_handle) { 803 | error = detail::last_error(); 804 | return {}; 805 | } 806 | char *mapping_start = static_cast(::MapViewOfFile( 807 | file_mapping_handle, mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, 808 | win::int64_high(aligned_offset), win::int64_low(aligned_offset), length_to_map)); 809 | if (mapping_start == nullptr) { 810 | // Close file handle if mapping it failed. 811 | ::CloseHandle(file_mapping_handle); 812 | error = detail::last_error(); 813 | return {}; 814 | } 815 | #else // POSIX 816 | char *mapping_start = 817 | static_cast(::mmap(0, // Don't give hint as to where to map. 818 | length_to_map, mode == access_mode::read ? PROT_READ : PROT_WRITE, 819 | MAP_SHARED, file_handle, aligned_offset)); 820 | if (mapping_start == MAP_FAILED) { 821 | error = detail::last_error(); 822 | return {}; 823 | } 824 | #endif 825 | mmap_context ctx; 826 | ctx.data = mapping_start + offset - aligned_offset; 827 | ctx.length = length; 828 | ctx.mapped_length = length_to_map; 829 | #ifdef _WIN32 830 | ctx.file_mapping_handle = file_mapping_handle; 831 | #endif 832 | return ctx; 833 | } 834 | 835 | } // namespace detail 836 | 837 | // -- basic_mmap -- 838 | 839 | template basic_mmap::~basic_mmap() { 840 | conditional_sync(); 841 | unmap(); 842 | } 843 | 844 | template 845 | basic_mmap::basic_mmap(basic_mmap &&other) 846 | : data_(std::move(other.data_)), length_(std::move(other.length_)), 847 | mapped_length_(std::move(other.mapped_length_)), file_handle_(std::move(other.file_handle_)) 848 | #ifdef _WIN32 849 | , 850 | file_mapping_handle_(std::move(other.file_mapping_handle_)) 851 | #endif 852 | , 853 | is_handle_internal_(std::move(other.is_handle_internal_)) { 854 | other.data_ = nullptr; 855 | other.length_ = other.mapped_length_ = 0; 856 | other.file_handle_ = invalid_handle; 857 | #ifdef _WIN32 858 | other.file_mapping_handle_ = invalid_handle; 859 | #endif 860 | } 861 | 862 | template 863 | basic_mmap &basic_mmap::operator=(basic_mmap &&other) { 864 | if (this != &other) { 865 | // First the existing mapping needs to be removed. 866 | unmap(); 867 | data_ = std::move(other.data_); 868 | length_ = std::move(other.length_); 869 | mapped_length_ = std::move(other.mapped_length_); 870 | file_handle_ = std::move(other.file_handle_); 871 | #ifdef _WIN32 872 | file_mapping_handle_ = std::move(other.file_mapping_handle_); 873 | #endif 874 | is_handle_internal_ = std::move(other.is_handle_internal_); 875 | 876 | // The moved from basic_mmap's fields need to be reset, because 877 | // otherwise other's destructor will unmap the same mapping that was 878 | // just moved into this. 879 | other.data_ = nullptr; 880 | other.length_ = other.mapped_length_ = 0; 881 | other.file_handle_ = invalid_handle; 882 | #ifdef _WIN32 883 | other.file_mapping_handle_ = invalid_handle; 884 | #endif 885 | other.is_handle_internal_ = false; 886 | } 887 | return *this; 888 | } 889 | 890 | template 891 | typename basic_mmap::handle_type 892 | basic_mmap::mapping_handle() const noexcept { 893 | #ifdef _WIN32 894 | return file_mapping_handle_; 895 | #else 896 | return file_handle_; 897 | #endif 898 | } 899 | 900 | template 901 | template 902 | void basic_mmap::map(const String &path, const size_type offset, 903 | const size_type length, std::error_code &error) { 904 | error.clear(); 905 | if (detail::empty(path)) { 906 | error = std::make_error_code(std::errc::invalid_argument); 907 | return; 908 | } 909 | const auto handle = detail::open_file(path, AccessMode, error); 910 | if (error) { 911 | return; 912 | } 913 | 914 | map(handle, offset, length, error); 915 | // This MUST be after the call to map, as that sets this to true. 916 | if (!error) { 917 | is_handle_internal_ = true; 918 | } 919 | } 920 | 921 | template 922 | void basic_mmap::map(const handle_type handle, const size_type offset, 923 | const size_type length, std::error_code &error) { 924 | error.clear(); 925 | if (handle == invalid_handle) { 926 | error = std::make_error_code(std::errc::bad_file_descriptor); 927 | return; 928 | } 929 | 930 | const auto file_size = detail::query_file_size(handle, error); 931 | if (error) { 932 | return; 933 | } 934 | 935 | if (offset + length > file_size) { 936 | error = std::make_error_code(std::errc::invalid_argument); 937 | return; 938 | } 939 | 940 | const auto ctx = detail::memory_map( 941 | handle, offset, length == map_entire_file ? (file_size - offset) : length, AccessMode, error); 942 | if (!error) { 943 | // We must unmap the previous mapping that may have existed prior to this call. 944 | // Note that this must only be invoked after a new mapping has been created in 945 | // order to provide the strong guarantee that, should the new mapping fail, the 946 | // `map` function leaves this instance in a state as though the function had 947 | // never been invoked. 948 | unmap(); 949 | file_handle_ = handle; 950 | is_handle_internal_ = false; 951 | data_ = reinterpret_cast(ctx.data); 952 | length_ = ctx.length; 953 | mapped_length_ = ctx.mapped_length; 954 | #ifdef _WIN32 955 | file_mapping_handle_ = ctx.file_mapping_handle; 956 | #endif 957 | } 958 | } 959 | 960 | template 961 | template 962 | typename std::enable_if::type 963 | basic_mmap::sync(std::error_code &error) { 964 | error.clear(); 965 | if (!is_open()) { 966 | error = std::make_error_code(std::errc::bad_file_descriptor); 967 | return; 968 | } 969 | 970 | if (data()) { 971 | #ifdef _WIN32 972 | if (::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0 || 973 | ::FlushFileBuffers(file_handle_) == 0) 974 | #else // POSIX 975 | if (::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0) 976 | #endif 977 | { 978 | error = detail::last_error(); 979 | return; 980 | } 981 | } 982 | #ifdef _WIN32 983 | if (::FlushFileBuffers(file_handle_) == 0) { 984 | error = detail::last_error(); 985 | } 986 | #endif 987 | } 988 | 989 | template void basic_mmap::unmap() { 990 | if (!is_open()) { 991 | return; 992 | } 993 | // TODO do we care about errors here? 994 | #ifdef _WIN32 995 | if (is_mapped()) { 996 | ::UnmapViewOfFile(get_mapping_start()); 997 | ::CloseHandle(file_mapping_handle_); 998 | } 999 | #else // POSIX 1000 | if (data_) { 1001 | ::munmap(const_cast(get_mapping_start()), mapped_length_); 1002 | } 1003 | #endif 1004 | 1005 | // If `file_handle_` was obtained by our opening it (when map is called with 1006 | // a path, rather than an existing file handle), we need to close it, 1007 | // otherwise it must not be closed as it may still be used outside this 1008 | // instance. 1009 | if (is_handle_internal_) { 1010 | #ifdef _WIN32 1011 | ::CloseHandle(file_handle_); 1012 | #else // POSIX 1013 | ::close(file_handle_); 1014 | #endif 1015 | } 1016 | 1017 | // Reset fields to their default values. 1018 | data_ = nullptr; 1019 | length_ = mapped_length_ = 0; 1020 | file_handle_ = invalid_handle; 1021 | #ifdef _WIN32 1022 | file_mapping_handle_ = invalid_handle; 1023 | #endif 1024 | } 1025 | 1026 | template 1027 | bool basic_mmap::is_mapped() const noexcept { 1028 | #ifdef _WIN32 1029 | return file_mapping_handle_ != invalid_handle; 1030 | #else // POSIX 1031 | return is_open(); 1032 | #endif 1033 | } 1034 | 1035 | template 1036 | void basic_mmap::swap(basic_mmap &other) { 1037 | if (this != &other) { 1038 | using std::swap; 1039 | swap(data_, other.data_); 1040 | swap(file_handle_, other.file_handle_); 1041 | #ifdef _WIN32 1042 | swap(file_mapping_handle_, other.file_mapping_handle_); 1043 | #endif 1044 | swap(length_, other.length_); 1045 | swap(mapped_length_, other.mapped_length_); 1046 | swap(is_handle_internal_, other.is_handle_internal_); 1047 | } 1048 | } 1049 | 1050 | template 1051 | template 1052 | typename std::enable_if::type 1053 | basic_mmap::conditional_sync() { 1054 | // This is invoked from the destructor, so not much we can do about 1055 | // failures here. 1056 | std::error_code ec; 1057 | sync(ec); 1058 | } 1059 | 1060 | template 1061 | template 1062 | typename std::enable_if::type 1063 | basic_mmap::conditional_sync() { 1064 | // noop 1065 | } 1066 | 1067 | template 1068 | bool operator==(const basic_mmap &a, const basic_mmap &b) { 1069 | return a.data() == b.data() && a.size() == b.size(); 1070 | } 1071 | 1072 | template 1073 | bool operator!=(const basic_mmap &a, const basic_mmap &b) { 1074 | return !(a == b); 1075 | } 1076 | 1077 | template 1078 | bool operator<(const basic_mmap &a, const basic_mmap &b) { 1079 | if (a.data() == b.data()) { 1080 | return a.size() < b.size(); 1081 | } 1082 | return a.data() < b.data(); 1083 | } 1084 | 1085 | template 1086 | bool operator<=(const basic_mmap &a, const basic_mmap &b) { 1087 | return !(a > b); 1088 | } 1089 | 1090 | template 1091 | bool operator>(const basic_mmap &a, const basic_mmap &b) { 1092 | if (a.data() == b.data()) { 1093 | return a.size() > b.size(); 1094 | } 1095 | return a.data() > b.data(); 1096 | } 1097 | 1098 | template 1099 | bool operator>=(const basic_mmap &a, const basic_mmap &b) { 1100 | return !(a < b); 1101 | } 1102 | 1103 | } // namespace mio 1104 | 1105 | #endif // MIO_BASIC_MMAP_IMPL 1106 | 1107 | #endif // MIO_MMAP_HEADER 1108 | /* Copyright 2017 https://github.com/mandreyel 1109 | * 1110 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 1111 | * software and associated documentation files (the "Software"), to deal in the Software 1112 | * without restriction, including without limitation the rights to use, copy, modify, 1113 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 1114 | * permit persons to whom the Software is furnished to do so, subject to the following 1115 | * conditions: 1116 | * 1117 | * The above copyright notice and this permission notice shall be included in all copies 1118 | * or substantial portions of the Software. 1119 | * 1120 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 1121 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 1122 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 1123 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 1124 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 1125 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1126 | */ 1127 | 1128 | #ifndef MIO_PAGE_HEADER 1129 | #define MIO_PAGE_HEADER 1130 | 1131 | #ifdef _WIN32 1132 | #include 1133 | #else 1134 | #include 1135 | #endif 1136 | 1137 | namespace mio { 1138 | 1139 | /** 1140 | * This is used by `basic_mmap` to determine whether to create a read-only or 1141 | * a read-write memory mapping. 1142 | */ 1143 | enum class access_mode { read, write }; 1144 | 1145 | /** 1146 | * Determines the operating system's page allocation granularity. 1147 | * 1148 | * On the first call to this function, it invokes the operating system specific syscall 1149 | * to determine the page size, caches the value, and returns it. Any subsequent call to 1150 | * this function serves the cached value, so no further syscalls are made. 1151 | */ 1152 | inline size_t page_size() { 1153 | static const size_t page_size = [] { 1154 | #ifdef _WIN32 1155 | SYSTEM_INFO SystemInfo; 1156 | GetSystemInfo(&SystemInfo); 1157 | return SystemInfo.dwAllocationGranularity; 1158 | #else 1159 | return sysconf(_SC_PAGE_SIZE); 1160 | #endif 1161 | }(); 1162 | return page_size; 1163 | } 1164 | 1165 | /** 1166 | * Alligns `offset` to the operating's system page size such that it subtracts the 1167 | * difference until the nearest page boundary before `offset`, or does nothing if 1168 | * `offset` is already page aligned. 1169 | */ 1170 | inline size_t make_offset_page_aligned(size_t offset) noexcept { 1171 | const size_t page_size_ = page_size(); 1172 | // Use integer division to round down to the nearest page alignment. 1173 | return offset / page_size_ * page_size_; 1174 | } 1175 | 1176 | } // namespace mio 1177 | 1178 | #endif // MIO_PAGE_HEADER 1179 | /* Copyright 2017 https://github.com/mandreyel 1180 | * 1181 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 1182 | * software and associated documentation files (the "Software"), to deal in the Software 1183 | * without restriction, including without limitation the rights to use, copy, modify, 1184 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 1185 | * permit persons to whom the Software is furnished to do so, subject to the following 1186 | * conditions: 1187 | * 1188 | * The above copyright notice and this permission notice shall be included in all copies 1189 | * or substantial portions of the Software. 1190 | * 1191 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 1192 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 1193 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 1194 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 1195 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 1196 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1197 | */ 1198 | 1199 | #ifndef MIO_SHARED_MMAP_HEADER 1200 | #define MIO_SHARED_MMAP_HEADER 1201 | 1202 | // #include "mio/mmap.hpp" 1203 | 1204 | #include // std::shared_ptr 1205 | #include // std::error_code 1206 | 1207 | namespace mio { 1208 | 1209 | /** 1210 | * Exposes (nearly) the same interface as `basic_mmap`, but endowes it with 1211 | * `std::shared_ptr` semantics. 1212 | * 1213 | * This is not the default behaviour of `basic_mmap` to avoid allocating on the heap if 1214 | * shared semantics are not required. 1215 | */ 1216 | template class basic_shared_mmap { 1217 | using impl_type = basic_mmap; 1218 | std::shared_ptr pimpl_; 1219 | 1220 | public: 1221 | using value_type = typename impl_type::value_type; 1222 | using size_type = typename impl_type::size_type; 1223 | using reference = typename impl_type::reference; 1224 | using const_reference = typename impl_type::const_reference; 1225 | using pointer = typename impl_type::pointer; 1226 | using const_pointer = typename impl_type::const_pointer; 1227 | using difference_type = typename impl_type::difference_type; 1228 | using iterator = typename impl_type::iterator; 1229 | using const_iterator = typename impl_type::const_iterator; 1230 | using reverse_iterator = typename impl_type::reverse_iterator; 1231 | using const_reverse_iterator = typename impl_type::const_reverse_iterator; 1232 | using iterator_category = typename impl_type::iterator_category; 1233 | using handle_type = typename impl_type::handle_type; 1234 | using mmap_type = impl_type; 1235 | 1236 | basic_shared_mmap() = default; 1237 | basic_shared_mmap(const basic_shared_mmap &) = default; 1238 | basic_shared_mmap &operator=(const basic_shared_mmap &) = default; 1239 | basic_shared_mmap(basic_shared_mmap &&) = default; 1240 | basic_shared_mmap &operator=(basic_shared_mmap &&) = default; 1241 | 1242 | /** Takes ownership of an existing mmap object. */ 1243 | basic_shared_mmap(mmap_type &&mmap) : pimpl_(std::make_shared(std::move(mmap))) {} 1244 | 1245 | /** Takes ownership of an existing mmap object. */ 1246 | basic_shared_mmap &operator=(mmap_type &&mmap) { 1247 | pimpl_ = std::make_shared(std::move(mmap)); 1248 | return *this; 1249 | } 1250 | 1251 | /** Initializes this object with an already established shared mmap. */ 1252 | basic_shared_mmap(std::shared_ptr mmap) : pimpl_(std::move(mmap)) {} 1253 | 1254 | /** Initializes this object with an already established shared mmap. */ 1255 | basic_shared_mmap &operator=(std::shared_ptr mmap) { 1256 | pimpl_ = std::move(mmap); 1257 | return *this; 1258 | } 1259 | 1260 | #ifdef __cpp_exceptions 1261 | /** 1262 | * The same as invoking the `map` function, except any error that may occur 1263 | * while establishing the mapping is wrapped in a `std::system_error` and is 1264 | * thrown. 1265 | */ 1266 | template 1267 | basic_shared_mmap(const String &path, const size_type offset = 0, 1268 | const size_type length = map_entire_file) { 1269 | std::error_code error; 1270 | map(path, offset, length, error); 1271 | if (error) { 1272 | throw std::system_error(error); 1273 | } 1274 | } 1275 | 1276 | /** 1277 | * The same as invoking the `map` function, except any error that may occur 1278 | * while establishing the mapping is wrapped in a `std::system_error` and is 1279 | * thrown. 1280 | */ 1281 | basic_shared_mmap(const handle_type handle, const size_type offset = 0, 1282 | const size_type length = map_entire_file) { 1283 | std::error_code error; 1284 | map(handle, offset, length, error); 1285 | if (error) { 1286 | throw std::system_error(error); 1287 | } 1288 | } 1289 | #endif // __cpp_exceptions 1290 | 1291 | /** 1292 | * If this is a read-write mapping and the last reference to the mapping, 1293 | * the destructor invokes sync. Regardless of the access mode, unmap is 1294 | * invoked as a final step. 1295 | */ 1296 | ~basic_shared_mmap() = default; 1297 | 1298 | /** Returns the underlying `std::shared_ptr` instance that holds the mmap. */ 1299 | std::shared_ptr get_shared_ptr() { return pimpl_; } 1300 | 1301 | /** 1302 | * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows, 1303 | * however, a mapped region of a file gets its own handle, which is returned by 1304 | * 'mapping_handle'. 1305 | */ 1306 | handle_type file_handle() const noexcept { 1307 | return pimpl_ ? pimpl_->file_handle() : invalid_handle; 1308 | } 1309 | 1310 | handle_type mapping_handle() const noexcept { 1311 | return pimpl_ ? pimpl_->mapping_handle() : invalid_handle; 1312 | } 1313 | 1314 | /** Returns whether a valid memory mapping has been created. */ 1315 | bool is_open() const noexcept { return pimpl_ && pimpl_->is_open(); } 1316 | 1317 | /** 1318 | * Returns true if no mapping was established, that is, conceptually the 1319 | * same as though the length that was mapped was 0. This function is 1320 | * provided so that this class has Container semantics. 1321 | */ 1322 | bool empty() const noexcept { return !pimpl_ || pimpl_->empty(); } 1323 | 1324 | /** 1325 | * `size` and `length` both return the logical length, i.e. the number of bytes 1326 | * user requested to be mapped, while `mapped_length` returns the actual number of 1327 | * bytes that were mapped which is a multiple of the underlying operating system's 1328 | * page allocation granularity. 1329 | */ 1330 | size_type size() const noexcept { return pimpl_ ? pimpl_->length() : 0; } 1331 | size_type length() const noexcept { return pimpl_ ? pimpl_->length() : 0; } 1332 | size_type mapped_length() const noexcept { return pimpl_ ? pimpl_->mapped_length() : 0; } 1333 | 1334 | /** 1335 | * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping 1336 | * exists. 1337 | */ 1338 | template ::type> 1340 | pointer data() noexcept { 1341 | return pimpl_->data(); 1342 | } 1343 | const_pointer data() const noexcept { return pimpl_ ? pimpl_->data() : nullptr; } 1344 | 1345 | /** 1346 | * Returns an iterator to the first requested byte, if a valid memory mapping 1347 | * exists, otherwise this function call is undefined behaviour. 1348 | */ 1349 | iterator begin() noexcept { return pimpl_->begin(); } 1350 | const_iterator begin() const noexcept { return pimpl_->begin(); } 1351 | const_iterator cbegin() const noexcept { return pimpl_->cbegin(); } 1352 | 1353 | /** 1354 | * Returns an iterator one past the last requested byte, if a valid memory mapping 1355 | * exists, otherwise this function call is undefined behaviour. 1356 | */ 1357 | template ::type> 1359 | iterator end() noexcept { 1360 | return pimpl_->end(); 1361 | } 1362 | const_iterator end() const noexcept { return pimpl_->end(); } 1363 | const_iterator cend() const noexcept { return pimpl_->cend(); } 1364 | 1365 | /** 1366 | * Returns a reverse iterator to the last memory mapped byte, if a valid 1367 | * memory mapping exists, otherwise this function call is undefined 1368 | * behaviour. 1369 | */ 1370 | template ::type> 1372 | reverse_iterator rbegin() noexcept { 1373 | return pimpl_->rbegin(); 1374 | } 1375 | const_reverse_iterator rbegin() const noexcept { return pimpl_->rbegin(); } 1376 | const_reverse_iterator crbegin() const noexcept { return pimpl_->crbegin(); } 1377 | 1378 | /** 1379 | * Returns a reverse iterator past the first mapped byte, if a valid memory 1380 | * mapping exists, otherwise this function call is undefined behaviour. 1381 | */ 1382 | template ::type> 1384 | reverse_iterator rend() noexcept { 1385 | return pimpl_->rend(); 1386 | } 1387 | const_reverse_iterator rend() const noexcept { return pimpl_->rend(); } 1388 | const_reverse_iterator crend() const noexcept { return pimpl_->crend(); } 1389 | 1390 | /** 1391 | * Returns a reference to the `i`th byte from the first requested byte (as returned 1392 | * by `data`). If this is invoked when no valid memory mapping has been created 1393 | * prior to this call, undefined behaviour ensues. 1394 | */ 1395 | reference operator[](const size_type i) noexcept { return (*pimpl_)[i]; } 1396 | const_reference operator[](const size_type i) const noexcept { return (*pimpl_)[i]; } 1397 | 1398 | /** 1399 | * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the 1400 | * reason is reported via `error` and the object remains in a state as if this 1401 | * function hadn't been called. 1402 | * 1403 | * `path`, which must be a path to an existing file, is used to retrieve a file 1404 | * handle (which is closed when the object destructs or `unmap` is called), which is 1405 | * then used to memory map the requested region. Upon failure, `error` is set to 1406 | * indicate the reason and the object remains in an unmapped state. 1407 | * 1408 | * `offset` is the number of bytes, relative to the start of the file, where the 1409 | * mapping should begin. When specifying it, there is no need to worry about 1410 | * providing a value that is aligned with the operating system's page allocation 1411 | * granularity. This is adjusted by the implementation such that the first requested 1412 | * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at 1413 | * `offset` from the start of the file. 1414 | * 1415 | * `length` is the number of bytes to map. It may be `map_entire_file`, in which 1416 | * case a mapping of the entire file is created. 1417 | */ 1418 | template 1419 | void map(const String &path, const size_type offset, const size_type length, 1420 | std::error_code &error) { 1421 | map_impl(path, offset, length, error); 1422 | } 1423 | 1424 | /** 1425 | * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the 1426 | * reason is reported via `error` and the object remains in a state as if this 1427 | * function hadn't been called. 1428 | * 1429 | * `path`, which must be a path to an existing file, is used to retrieve a file 1430 | * handle (which is closed when the object destructs or `unmap` is called), which is 1431 | * then used to memory map the requested region. Upon failure, `error` is set to 1432 | * indicate the reason and the object remains in an unmapped state. 1433 | * 1434 | * The entire file is mapped. 1435 | */ 1436 | template void map(const String &path, std::error_code &error) { 1437 | map_impl(path, 0, map_entire_file, error); 1438 | } 1439 | 1440 | /** 1441 | * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the 1442 | * reason is reported via `error` and the object remains in a state as if this 1443 | * function hadn't been called. 1444 | * 1445 | * `handle`, which must be a valid file handle, which is used to memory map the 1446 | * requested region. Upon failure, `error` is set to indicate the reason and the 1447 | * object remains in an unmapped state. 1448 | * 1449 | * `offset` is the number of bytes, relative to the start of the file, where the 1450 | * mapping should begin. When specifying it, there is no need to worry about 1451 | * providing a value that is aligned with the operating system's page allocation 1452 | * granularity. This is adjusted by the implementation such that the first requested 1453 | * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at 1454 | * `offset` from the start of the file. 1455 | * 1456 | * `length` is the number of bytes to map. It may be `map_entire_file`, in which 1457 | * case a mapping of the entire file is created. 1458 | */ 1459 | void map(const handle_type handle, const size_type offset, const size_type length, 1460 | std::error_code &error) { 1461 | map_impl(handle, offset, length, error); 1462 | } 1463 | 1464 | /** 1465 | * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the 1466 | * reason is reported via `error` and the object remains in a state as if this 1467 | * function hadn't been called. 1468 | * 1469 | * `handle`, which must be a valid file handle, which is used to memory map the 1470 | * requested region. Upon failure, `error` is set to indicate the reason and the 1471 | * object remains in an unmapped state. 1472 | * 1473 | * The entire file is mapped. 1474 | */ 1475 | void map(const handle_type handle, std::error_code &error) { 1476 | map_impl(handle, 0, map_entire_file, error); 1477 | } 1478 | 1479 | /** 1480 | * If a valid memory mapping has been created prior to this call, this call 1481 | * instructs the kernel to unmap the memory region and disassociate this object 1482 | * from the file. 1483 | * 1484 | * The file handle associated with the file that is mapped is only closed if the 1485 | * mapping was created using a file path. If, on the other hand, an existing 1486 | * file handle was used to create the mapping, the file handle is not closed. 1487 | */ 1488 | void unmap() { 1489 | if (pimpl_) 1490 | pimpl_->unmap(); 1491 | } 1492 | 1493 | void swap(basic_shared_mmap &other) { pimpl_.swap(other.pimpl_); } 1494 | 1495 | /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ 1496 | template ::type> 1498 | void sync(std::error_code &error) { 1499 | if (pimpl_) 1500 | pimpl_->sync(error); 1501 | } 1502 | 1503 | /** All operators compare the underlying `basic_mmap`'s addresses. */ 1504 | 1505 | friend bool operator==(const basic_shared_mmap &a, const basic_shared_mmap &b) { 1506 | return a.pimpl_ == b.pimpl_; 1507 | } 1508 | 1509 | friend bool operator!=(const basic_shared_mmap &a, const basic_shared_mmap &b) { 1510 | return !(a == b); 1511 | } 1512 | 1513 | friend bool operator<(const basic_shared_mmap &a, const basic_shared_mmap &b) { 1514 | return a.pimpl_ < b.pimpl_; 1515 | } 1516 | 1517 | friend bool operator<=(const basic_shared_mmap &a, const basic_shared_mmap &b) { 1518 | return a.pimpl_ <= b.pimpl_; 1519 | } 1520 | 1521 | friend bool operator>(const basic_shared_mmap &a, const basic_shared_mmap &b) { 1522 | return a.pimpl_ > b.pimpl_; 1523 | } 1524 | 1525 | friend bool operator>=(const basic_shared_mmap &a, const basic_shared_mmap &b) { 1526 | return a.pimpl_ >= b.pimpl_; 1527 | } 1528 | 1529 | private: 1530 | template 1531 | void map_impl(const MappingToken &token, const size_type offset, const size_type length, 1532 | std::error_code &error) { 1533 | if (!pimpl_) { 1534 | mmap_type mmap = make_mmap(token, offset, length, error); 1535 | if (error) { 1536 | return; 1537 | } 1538 | pimpl_ = std::make_shared(std::move(mmap)); 1539 | } else { 1540 | pimpl_->map(token, offset, length, error); 1541 | } 1542 | } 1543 | }; 1544 | 1545 | /** 1546 | * This is the basis for all read-only mmap objects and should be preferred over 1547 | * directly using basic_shared_mmap. 1548 | */ 1549 | template 1550 | using basic_shared_mmap_source = basic_shared_mmap; 1551 | 1552 | /** 1553 | * This is the basis for all read-write mmap objects and should be preferred over 1554 | * directly using basic_shared_mmap. 1555 | */ 1556 | template 1557 | using basic_shared_mmap_sink = basic_shared_mmap; 1558 | 1559 | /** 1560 | * These aliases cover the most common use cases, both representing a raw byte stream 1561 | * (either with a char or an unsigned char/uint8_t). 1562 | */ 1563 | using shared_mmap_source = basic_shared_mmap_source; 1564 | using shared_ummap_source = basic_shared_mmap_source; 1565 | 1566 | using shared_mmap_sink = basic_shared_mmap_sink; 1567 | using shared_ummap_sink = basic_shared_mmap_sink; 1568 | 1569 | } // namespace mio 1570 | 1571 | #endif // MIO_SHARED_MMAP_HEADER 1572 | --------------------------------------------------------------------------------