Coverage Report

Created: 2025-12-14 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libtorrent/src/mmap.cpp
Line
Count
Source
1
/*
2
3
Copyright (c) 2016, 2019-2022, Arvid Norberg
4
Copyright (c) 2019, Steven Siloti
5
Copyright (c) 2020, Tiger Wang
6
Copyright (c) 2021, Alden Torres
7
All rights reserved.
8
9
Redistribution and use in source and binary forms, with or without
10
modification, are permitted provided that the following conditions
11
are met:
12
13
    * Redistributions of source code must retain the above copyright
14
      notice, this list of conditions and the following disclaimer.
15
    * Redistributions in binary form must reproduce the above copyright
16
      notice, this list of conditions and the following disclaimer in
17
      the documentation and/or other materials provided with the distribution.
18
    * Neither the name of the author nor the names of its
19
      contributors may be used to endorse or promote products derived
20
      from this software without specific prior written permission.
21
22
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
POSSIBILITY OF SUCH DAMAGE.
33
34
*/
35
36
#include "libtorrent/config.hpp"
37
38
#if TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE
39
40
#include "libtorrent/aux_/mmap.hpp"
41
#include "libtorrent/aux_/throw.hpp"
42
#include "libtorrent/aux_/path.hpp"
43
#include "libtorrent/error_code.hpp"
44
#include "libtorrent/file.hpp" // for file_handle
45
46
#include <cstdint>
47
48
#ifdef TORRENT_WINDOWS
49
#include "libtorrent/aux_/win_util.hpp"
50
#endif
51
52
#if TORRENT_HAVE_MMAP
53
#include <sys/mman.h> // for mmap
54
#include <sys/stat.h>
55
#include <fcntl.h> // for open
56
57
#include "libtorrent/aux_/disable_warnings_push.hpp"
58
auto const map_failed = MAP_FAILED;
59
#include "libtorrent/aux_/disable_warnings_pop.hpp"
60
#endif
61
62
#if TORRENT_USE_SYNC_FILE_RANGE
63
#include <fcntl.h> // for sync_file_range
64
#endif
65
66
namespace libtorrent {
67
namespace aux {
68
69
namespace {
70
  std::int64_t memory_map_size(open_mode_t const mode
71
    , std::int64_t const file_size, file_handle const& fh)
72
0
  {
73
    // if we're opening the file in write-mode, we'll always truncate it to
74
    // the right size, but in read mode, we should not map more than the
75
    // file size
76
0
    return (mode & open_mode::write)
77
0
      ? file_size : std::min(std::int64_t(fh.get_size()), file_size);
78
0
  }
79
80
} // anonymous
81
82
#if !TORRENT_HAVE_MAP_VIEW_OF_FILE
83
84
namespace {
85
86
  int mmap_prot(open_mode_t const m)
87
0
  {
88
0
    return (m & open_mode::write)
89
0
      ? (PROT_READ | PROT_WRITE)
90
0
      : PROT_READ;
91
0
  }
92
93
  int mmap_flags(open_mode_t const m)
94
0
  {
95
0
    TORRENT_UNUSED(m);
96
0
    return
97
0
      MAP_FILE | MAP_SHARED
98
#ifdef MAP_NOCACHE
99
      | ((m & open_mode::no_cache) ? MAP_NOCACHE : 0)
100
#endif
101
#ifdef MAP_NOCORE
102
      // BSD has a flag to exclude this region from core files
103
      | MAP_NOCORE
104
#endif
105
0
      ;
106
0
  }
107
108
} // anonymous
109
110
#endif
111
112
#if TORRENT_HAVE_MAP_VIEW_OF_FILE
113
114
// ======== file mapping handle ========
115
namespace {
116
117
int map_protect(open_mode_t const m)
118
{
119
  return (m & open_mode::write) ? PAGE_READWRITE : PAGE_READONLY;
120
}
121
122
}
123
124
file_mapping_handle::file_mapping_handle(file_handle file, open_mode_t const mode
125
  , std::int64_t const size)
126
  : m_file(std::move(file))
127
  , m_mapping(CreateFileMapping(m_file.fd()
128
    , nullptr
129
    , map_protect(mode)
130
    , size >> 32
131
    , size & 0xffffffff
132
    , nullptr))
133
  {
134
    TORRENT_ASSERT(size >= 0);
135
    // CreateFileMapping will extend the underlying file to the specified // size.
136
    // you can't map files of size 0, so we just set it to null. We
137
    // still need to create the empty file.
138
    if (size > 0 && m_mapping == nullptr)
139
      throw_ex<system_error>(error_code(GetLastError(), system_category()));
140
  }
141
  file_mapping_handle::~file_mapping_handle() { close(); }
142
143
  file_mapping_handle::file_mapping_handle(file_mapping_handle&& fm)
144
    : m_file(std::move(fm.m_file)), m_mapping(fm.m_mapping)
145
  {
146
    fm.m_mapping = INVALID_HANDLE_VALUE;
147
  }
148
149
  file_mapping_handle& file_mapping_handle::operator=(file_mapping_handle&& fm) &
150
  {
151
    if (&fm == this) return *this;
152
    close();
153
    m_file = std::move(fm.m_file);
154
    m_mapping = fm.m_mapping;
155
    fm.m_mapping = INVALID_HANDLE_VALUE;
156
    return *this;
157
  }
158
159
  void file_mapping_handle::close()
160
  {
161
    if (m_mapping == INVALID_HANDLE_VALUE) return;
162
    CloseHandle(m_mapping);
163
    m_mapping = INVALID_HANDLE_VALUE;
164
  }
165
166
#endif // HAVE_MAP_VIEW_OF_FILE
167
168
// =========== file mapping ============
169
170
#if TORRENT_HAVE_MMAP
171
172
file_mapping::file_mapping(file_handle file, open_mode_t const mode, std::int64_t const file_size)
173
0
  : m_size(memory_map_size(mode, file_size, file))
174
0
  , m_file(std::move(file))
175
0
  , m_mapping((mode & open_mode::no_mmap) || m_size == 0 ? nullptr
176
0
    : mmap(nullptr, static_cast<std::size_t>(m_size)
177
0
      , mmap_prot(mode), mmap_flags(mode), m_file.fd(), 0))
178
0
{
179
0
  TORRENT_ASSERT(file_size >= 0);
180
  // you can't create an mmap of size 0, so we just set it to null. We
181
  // still need to create the empty file.
182
0
  if (!(mode & open_mode::no_mmap) && m_mapping == map_failed)
183
0
  {
184
0
    throw_ex<storage_error>(error_code(errno, system_category()), operation_t::file_mmap);
185
0
  }
186
187
0
#if TORRENT_USE_MADVISE
188
0
  if (m_mapping != nullptr && m_mapping != map_failed)
189
0
  {
190
0
    int const advise = ((mode & open_mode::sequential_access) ? MADV_SEQUENTIAL : 0)
191
0
#ifdef MADV_DONTDUMP
192
    // on versions of linux that support it, ask for this region to not be
193
    // included in coredumps (mostly to make the coredumps more manageable
194
    // with large disk caches)
195
    // ignore errors here, since this is best-effort
196
0
      | MADV_DONTDUMP
197
0
#endif
198
0
#ifdef MADV_DONTFORK
199
0
      | MADV_DONTFORK
200
0
#endif
201
#ifdef MADV_NOCORE
202
    // This is the BSD counterpart to exclude a range from core dumps
203
      | MADV_NOCORE
204
#endif
205
0
    ;
206
0
    if (advise != 0)
207
0
      ::madvise(m_mapping, static_cast<std::size_t>(m_size), advise);
208
0
  }
209
0
#endif
210
0
}
211
212
void file_mapping::close()
213
0
{
214
0
  if (m_mapping == nullptr) return;
215
0
  munmap(m_mapping, static_cast<std::size_t>(m_size));
216
0
  m_mapping = nullptr;
217
0
}
218
219
#else
220
221
namespace {
222
DWORD map_access(open_mode_t const m)
223
{
224
  return (m & open_mode::write) ? FILE_MAP_READ | FILE_MAP_WRITE : FILE_MAP_READ ;
225
}
226
} // anonymous
227
228
file_mapping::file_mapping(file_handle file, open_mode_t const mode
229
  , std::int64_t const file_size
230
  , std::shared_ptr<std::mutex> open_unmap_lock)
231
  : m_size(memory_map_size(mode, file_size, file))
232
  , m_file(std::move(file), mode, m_size)
233
  , m_open_unmap_lock(std::move(open_unmap_lock))
234
  , m_mapping((mode & open_mode::no_mmap) || m_size == 0 ? nullptr
235
    : MapViewOfFile(m_file.handle(), map_access(mode), 0, 0, static_cast<std::size_t>(m_size)))
236
{
237
  // you can't create an mmap of size 0, so we just set it to null. We
238
  // still need to create the empty file.
239
  if (!(mode & open_mode::no_mmap) && m_size > 0 && m_mapping == nullptr)
240
    throw_ex<storage_error>(error_code(GetLastError(), system_category()), operation_t::file_mmap);
241
}
242
243
void file_mapping::flush()
244
{
245
  if (m_mapping == nullptr) return;
246
247
  // ignore errors, this is best-effort
248
  FlushViewOfFile(m_mapping, static_cast<std::size_t>(m_size));
249
}
250
251
void file_mapping::close()
252
{
253
  if (m_mapping == nullptr) return;
254
  flush();
255
  std::lock_guard<std::mutex> l(*m_open_unmap_lock);
256
  UnmapViewOfFile(m_mapping);
257
  m_mapping = nullptr;
258
}
259
#endif
260
261
file_mapping::file_mapping(file_mapping&& rhs)
262
0
  : m_size(rhs.m_size)
263
0
  , m_file(std::move(rhs.m_file))
264
0
  , m_mapping(rhs.m_mapping)
265
0
  {
266
0
    TORRENT_ASSERT(m_mapping);
267
0
    rhs.m_mapping = nullptr;
268
0
  }
269
270
file_mapping& file_mapping::operator=(file_mapping&& rhs) &
271
0
{
272
0
  if (&rhs == this) return *this;
273
0
  close();
274
0
  m_file = std::move(rhs.m_file);
275
0
  m_size = rhs.m_size;
276
0
  m_mapping = rhs.m_mapping;
277
0
  rhs.m_mapping = nullptr;
278
0
  return *this;
279
0
}
280
281
0
file_mapping::~file_mapping() { close(); }
282
283
void file_mapping::dont_need(span<byte const> range)
284
0
{
285
0
  auto* const start = const_cast<byte*>(range.data());
286
0
  auto const size = static_cast<std::size_t>(range.size());
287
288
0
#if TORRENT_USE_MADVISE
289
0
  int const advise = 0
290
0
#if defined TORRENT_LINUX && defined MADV_COLD
291
0
    | MADV_COLD
292
#elif !defined TORRENT_LINUX && defined MADV_DONTNEED
293
    // note that MADV_DONTNEED is broken on Linux. It can destroy data. We
294
    // cannot use it
295
    | MADV_DONTNEED
296
#endif
297
0
  ;
298
299
0
  if (advise)
300
0
    ::madvise(start, size, advise);
301
0
#endif
302
0
#ifndef TORRENT_WINDOWS
303
0
  ::msync(start, size, MS_INVALIDATE);
304
#else
305
  TORRENT_UNUSED(start);
306
  TORRENT_UNUSED(size);
307
#endif
308
0
}
309
310
void file_mapping::page_out(span<byte const> range)
311
0
{
312
#if TORRENT_HAVE_MAP_VIEW_OF_FILE
313
  // ignore errors, this is best-effort
314
  FlushViewOfFile(range.data(), static_cast<std::size_t>(range.size()));
315
#else
316
317
0
  auto* const start = const_cast<byte*>(range.data());
318
0
  auto const size = static_cast<std::size_t>(range.size());
319
0
#if TORRENT_USE_MADVISE && defined MADV_PAGEOUT
320
0
  ::madvise(start, size, MADV_PAGEOUT);
321
#elif TORRENT_USE_SYNC_FILE_RANGE
322
  // this is best-effort. ignore errors
323
  ::sync_file_range(m_file.fd(), start - static_cast<const byte*>(m_mapping)
324
    , size, SYNC_FILE_RANGE_WRITE);
325
#endif
326
327
  // msync(MS_ASYNC) is a no-op on Linux > 2.6.19.
328
0
  ::msync(start, size, MS_ASYNC);
329
330
0
#endif // MAP_VIEW_OF_FILE
331
0
}
332
333
} // aux
334
} // libtorrent
335
336
#endif // HAVE_MMAP || HAVE_MAP_VIEW_OF_FILE
337