Coverage Report

Created: 2025-07-18 06:49

/src/libtorrent/src/file.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
3
Copyright (c) 2018, d-komarov
4
Copyright (c) 2004-2005, 2007-2020, 2022, Arvid Norberg
5
Copyright (c) 2016, Alden Torres
6
Copyright (c) 2016-2017, Andrei Kurushin
7
Copyright (c) 2016-2017, Steven Siloti
8
Copyright (c) 2020, Tiger Wang
9
All rights reserved.
10
11
Redistribution and use in source and binary forms, with or without
12
modification, are permitted provided that the following conditions
13
are met:
14
15
    * Redistributions of source code must retain the above copyright
16
      notice, this list of conditions and the following disclaimer.
17
    * Redistributions in binary form must reproduce the above copyright
18
      notice, this list of conditions and the following disclaimer in
19
      the documentation and/or other materials provided with the distribution.
20
    * Neither the name of the author nor the names of its
21
      contributors may be used to endorse or promote products derived
22
      from this software without specific prior written permission.
23
24
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
28
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34
POSSIBILITY OF SUCH DAMAGE.
35
36
*/
37
38
#include "libtorrent/config.hpp"
39
#include "libtorrent/span.hpp"
40
#include <mutex> // for call_once
41
42
#ifdef __GNUC__
43
#pragma GCC diagnostic push
44
#pragma GCC diagnostic ignored "-Wunused-macros"
45
#endif
46
47
#ifdef __clang__
48
#pragma clang diagnostic push
49
#pragma clang diagnostic ignored "-Wunknown-pragmas"
50
#pragma clang diagnostic ignored "-Wunused-macros"
51
#pragma clang diagnostic ignored "-Wreserved-id-macro"
52
#endif
53
54
// on mingw this is necessary to enable 64-bit time_t, specifically used for
55
// the stat struct. Without this, modification times returned by stat may be
56
// incorrect and consistently fail resume data
57
#ifndef __MINGW_USE_VC2005_COMPAT
58
# define __MINGW_USE_VC2005_COMPAT
59
#endif
60
61
#ifdef __clang__
62
#pragma clang diagnostic pop
63
#endif
64
65
#ifdef __GNUC__
66
#pragma GCC diagnostic pop
67
#endif
68
69
#include "libtorrent/file.hpp"
70
#include "libtorrent/aux_/path.hpp" // for convert_to_native_path_string
71
#include "libtorrent/string_util.hpp"
72
#include <cstring>
73
74
#include "libtorrent/assert.hpp"
75
#include "libtorrent/aux_/throw.hpp"
76
#include "libtorrent/aux_/open_mode.hpp"
77
#include "libtorrent/error_code.hpp"
78
79
#include "libtorrent/aux_/disable_warnings_push.hpp"
80
81
#include <sys/stat.h>
82
83
#ifdef TORRENT_WINDOWS
84
// windows part
85
86
#include "libtorrent/aux_/win_util.hpp"
87
#include "libtorrent/aux_/scope_end.hpp"
88
89
#ifndef WIN32_LEAN_AND_MEAN
90
#define WIN32_LEAN_AND_MEAN
91
#endif
92
#include <windows.h>
93
#include <winioctl.h>
94
#include <sys/types.h>
95
#else
96
// posix part
97
98
#include <unistd.h>
99
#include <sys/types.h>
100
#include <cerrno>
101
#include <dirent.h>
102
103
#include <boost/asio/error.hpp> // for boost::asio::error::eof
104
105
#ifdef TORRENT_LINUX
106
// linux specifics
107
108
#include <sys/ioctl.h>
109
#ifdef TORRENT_ANDROID
110
#include <sys/syscall.h>
111
#endif
112
113
#endif
114
115
#endif // posix part
116
117
#include "libtorrent/aux_/disable_warnings_pop.hpp"
118
119
namespace libtorrent {
120
namespace aux {
121
122
#ifdef TORRENT_WINDOWS
123
  int pread_all(handle_type const fd
124
    , span<char> const buf
125
    , std::int64_t const offset
126
    , error_code& ec)
127
  {
128
    OVERLAPPED ol{};
129
    ol.Offset = offset & 0xffffffff;
130
    ol.OffsetHigh = offset >> 32;
131
    DWORD bytes_read = 0;
132
    if (ReadFile(fd, buf.data(), DWORD(buf.size()), &bytes_read, &ol) == FALSE)
133
    {
134
      ec = error_code(::GetLastError(), system_category());
135
      return -1;
136
    }
137
138
    return int(bytes_read);
139
  }
140
141
  int pwrite_all(handle_type const fd
142
    , span<char const> const buf
143
    , std::int64_t const offset
144
    , error_code& ec)
145
  {
146
    OVERLAPPED ol{};
147
    ol.Offset = offset & 0xffffffff;
148
    ol.OffsetHigh = offset >> 32;
149
    DWORD bytes_written = 0;
150
    if (WriteFile(fd, buf.data(), DWORD(buf.size()), &bytes_written, &ol) == FALSE)
151
    {
152
      ec = error_code(::GetLastError(), system_category());
153
      return -1;
154
    }
155
156
    return int(bytes_written);
157
  }
158
#else
159
160
  int pread_all(handle_type const handle
161
    , span<char> buf
162
    , std::int64_t file_offset
163
    , error_code& ec)
164
0
  {
165
0
    int ret = 0;
166
0
    do {
167
0
      auto const r = ::pread(handle, buf.data(), std::size_t(buf.size()), file_offset);
168
0
      if (r == 0)
169
0
      {
170
0
        ec = boost::asio::error::eof;
171
0
        return ret;
172
0
      }
173
0
      if (r < 0)
174
0
      {
175
0
        ec = error_code(errno, system_category());
176
0
        return ret;
177
0
      }
178
0
      ret += r;
179
0
      file_offset += r;
180
0
      buf = buf.subspan(r);
181
0
    } while (buf.size() > 0);
182
0
    return ret;
183
0
  }
184
185
  int pwrite_all(handle_type const handle
186
    , span<char const> buf
187
    , std::int64_t file_offset
188
    , error_code& ec)
189
0
  {
190
0
    int ret = 0;
191
0
    do {
192
0
      auto const r = ::pwrite(handle, buf.data(), std::size_t(buf.size()), file_offset);
193
0
      if (r == 0)
194
0
      {
195
0
        ec = boost::asio::error::eof;
196
0
        return ret;
197
0
      }
198
0
      if (r < 0)
199
0
      {
200
0
        ec = error_code(errno, system_category());
201
0
        return -1;
202
0
      }
203
0
      ret += r;
204
0
      file_offset += r;
205
0
      buf = buf.subspan(r);
206
0
    } while (buf.size() > 0);
207
0
    return ret;
208
0
  }
209
#endif
210
211
namespace {
212
#ifdef TORRENT_WINDOWS
213
  // returns true if the given file has any regions that are
214
  // sparse, i.e. not allocated.
215
  bool is_sparse(HANDLE file)
216
  {
217
    LARGE_INTEGER file_size;
218
    if (!GetFileSizeEx(file, &file_size))
219
      return false;
220
221
#ifndef FSCTL_QUERY_ALLOCATED_RANGES
222
typedef struct _FILE_ALLOCATED_RANGE_BUFFER {
223
  LARGE_INTEGER FileOffset;
224
  LARGE_INTEGER Length;
225
} FILE_ALLOCATED_RANGE_BUFFER;
226
#define FSCTL_QUERY_ALLOCATED_RANGES ((0x9 << 16) | (1 << 14) | (51 << 2) | 3)
227
#endif
228
    FILE_ALLOCATED_RANGE_BUFFER in;
229
    in.FileOffset.QuadPart = 0;
230
    in.Length.QuadPart = file_size.QuadPart;
231
232
    FILE_ALLOCATED_RANGE_BUFFER out[2];
233
234
    DWORD returned_bytes = 0;
235
    BOOL ret = DeviceIoControl(file, FSCTL_QUERY_ALLOCATED_RANGES, static_cast<void*>(&in), sizeof(in)
236
      , out, sizeof(out), &returned_bytes, nullptr);
237
238
    if (ret == FALSE)
239
    {
240
      return true;
241
    }
242
243
    // if we have more than one range in the file, we're sparse
244
    if (returned_bytes != sizeof(FILE_ALLOCATED_RANGE_BUFFER)) {
245
      return true;
246
    }
247
248
    return (in.Length.QuadPart != out[0].Length.QuadPart);
249
  }
250
251
  DWORD file_access(open_mode_t const mode)
252
  {
253
    return (mode & open_mode::write)
254
      ? GENERIC_WRITE | GENERIC_READ
255
      : GENERIC_READ;
256
  }
257
258
  DWORD file_create(open_mode_t const mode)
259
  {
260
    return (mode & open_mode::write) ? OPEN_ALWAYS : OPEN_EXISTING;
261
  }
262
263
264
#ifndef TORRENT_WINRT
265
  std::once_flag g_once_flag;
266
267
  void acquire_manage_volume_privs()
268
  {
269
    using OpenProcessToken_t = BOOL (WINAPI*)(HANDLE, DWORD, PHANDLE);
270
271
    using LookupPrivilegeValue_t = BOOL (WINAPI*)(LPCSTR, LPCSTR, PLUID);
272
273
    using AdjustTokenPrivileges_t = BOOL (WINAPI*)(
274
      HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD);
275
276
    auto OpenProcessToken =
277
      aux::get_library_procedure<aux::advapi32, OpenProcessToken_t>("OpenProcessToken");
278
    auto LookupPrivilegeValue =
279
      aux::get_library_procedure<aux::advapi32, LookupPrivilegeValue_t>("LookupPrivilegeValueA");
280
    auto AdjustTokenPrivileges =
281
      aux::get_library_procedure<aux::advapi32, AdjustTokenPrivileges_t>("AdjustTokenPrivileges");
282
283
    if (OpenProcessToken == nullptr
284
      || LookupPrivilegeValue == nullptr
285
      || AdjustTokenPrivileges == nullptr)
286
    {
287
      return;
288
    }
289
290
    HANDLE token;
291
    if (!OpenProcessToken(GetCurrentProcess()
292
      , TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))
293
      return;
294
295
    auto close_handle = aux::scope_end([&token] {
296
      CloseHandle(token);
297
    });
298
299
    TOKEN_PRIVILEGES privs{};
300
    if (!LookupPrivilegeValue(nullptr, "SeManageVolumePrivilege"
301
      , &privs.Privileges[0].Luid))
302
    {
303
      return;
304
    }
305
306
    privs.PrivilegeCount = 1;
307
    privs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
308
309
    AdjustTokenPrivileges(token, FALSE, &privs, 0, nullptr, nullptr);
310
  }
311
#endif // TORRENT_WINRT
312
313
#ifdef TORRENT_WINRT
314
315
  DWORD file_flags(open_mode_t const mode)
316
  {
317
    return ((mode & open_mode::no_cache) ? FILE_FLAG_WRITE_THROUGH : 0)
318
      | ((mode & open_mode::sequential_access) ? FILE_FLAG_SEQUENTIAL_SCAN : 0)
319
      ;
320
  }
321
322
  DWORD file_attributes(open_mode_t const mode)
323
  {
324
    return (mode & open_mode::hidden) ? FILE_ATTRIBUTE_HIDDEN : FILE_ATTRIBUTE_NORMAL;
325
  }
326
327
  auto create_file(const native_path_string & name, open_mode_t const mode)
328
  {
329
    CREATEFILE2_EXTENDED_PARAMETERS Extended
330
    {
331
      sizeof(CREATEFILE2_EXTENDED_PARAMETERS),
332
      file_attributes(mode),
333
      file_flags(mode)
334
    };
335
336
    return CreateFile2(name.c_str()
337
      , file_access(mode)
338
      , FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE
339
      , file_create(mode)
340
      , &Extended);
341
  }
342
343
#else
344
345
  DWORD file_flags(open_mode_t const mode)
346
  {
347
    // one might think it's a good idea to pass in FILE_FLAG_RANDOM_ACCESS. It
348
    // turns out that it isn't. That flag will break your operating system:
349
    // http://support.microsoft.com/kb/2549369
350
    return ((mode & open_mode::hidden) ? FILE_ATTRIBUTE_HIDDEN : FILE_ATTRIBUTE_NORMAL)
351
      | ((mode & open_mode::no_cache) ? FILE_FLAG_WRITE_THROUGH : 0)
352
      | ((mode & open_mode::sequential_access) ? FILE_FLAG_SEQUENTIAL_SCAN : 0)
353
      ;
354
  }
355
356
  auto create_file(const native_path_string & name, open_mode_t const mode)
357
  {
358
359
    if (mode & aux::open_mode::allow_set_file_valid_data)
360
    {
361
      // Enable privilege required by SetFileValidData()
362
      // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilevaliddata
363
      // This must happen before the file is opened:
364
      // https://devblogs.microsoft.com/oldnewthing/20160603-00/?p=93565
365
      // SetFileValidData() is not available on WinRT, so there is no
366
      // corresponding call in that version of create_file()
367
      std::call_once(g_once_flag, acquire_manage_volume_privs);
368
    }
369
370
    return CreateFileW(name.c_str()
371
      , file_access(mode)
372
      , FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE
373
      , nullptr
374
      , file_create(mode)
375
      , file_flags(mode)
376
      , nullptr);
377
  }
378
379
#endif
380
381
#else
382
  // non-windows
383
384
  int file_flags(open_mode_t const mode)
385
0
  {
386
0
    return ((mode & open_mode::write) ? O_RDWR | O_CREAT : O_RDONLY)
387
0
#ifdef O_CLOEXEC
388
0
      | O_CLOEXEC
389
0
#endif
390
0
#ifdef O_NOATIME
391
0
      | ((mode & open_mode::no_atime) ? O_NOATIME : 0)
392
0
#endif
393
0
#ifdef O_SYNC
394
0
      | ((mode & open_mode::no_cache) ? O_SYNC : 0)
395
0
#endif
396
0
      ;
397
0
  }
398
399
  mode_t file_perms(open_mode_t const mode)
400
0
  {
401
    // rely on default umask to filter x and w permissions
402
    // for group and others
403
0
    mode_t permissions = S_IRUSR | S_IWUSR
404
0
      | S_IRGRP | S_IWGRP
405
0
      | S_IROTH | S_IWOTH;
406
407
0
    if ((mode & aux::open_mode::executable))
408
0
      permissions |= S_IXGRP | S_IXOTH | S_IXUSR;
409
410
0
    return permissions;
411
0
  }
412
413
  int open_file(std::string const filename, open_mode_t const mode)
414
0
  {
415
0
    int ret = ::open(filename.c_str(), file_flags(mode), file_perms(mode));
416
417
0
#ifdef O_NOATIME
418
0
    if (ret < 0 && (mode & open_mode::no_atime))
419
0
    {
420
      // NOATIME may not be allowed for certain files, it's best-effort,
421
      // so just try again without NOATIME
422
0
      ret = ::open(filename.c_str()
423
0
        , file_flags(mode & ~open_mode::no_atime), file_perms(mode));
424
0
    }
425
0
#endif
426
0
    if (ret < 0) throw_ex<storage_error>(error_code(errno, system_category()), operation_t::file_open);
427
0
    return ret;
428
0
  }
429
#endif // TORRENT_WINDOWS
430
431
} // anonymous namespace
432
433
#ifdef TORRENT_WINDOWS
434
file_handle::file_handle(string_view name, std::int64_t const size
435
  , open_mode_t const mode)
436
  : m_fd(create_file(convert_to_native_path_string(name.to_string()), mode))
437
  , m_open_mode(mode)
438
{
439
  if (m_fd == invalid_handle)
440
  {
441
    throw_ex<storage_error>(error_code(GetLastError(), system_category())
442
      , operation_t::file_open);
443
  }
444
445
  // try to make the file sparse if supported
446
  // only set this flag if the file is opened for writing
447
  if ((mode & aux::open_mode::sparse) && (mode & aux::open_mode::write))
448
  {
449
    DWORD temp;
450
    ::DeviceIoControl(m_fd, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &temp, nullptr);
451
  }
452
453
  if ((mode & open_mode::truncate)
454
    && !(mode & aux::open_mode::sparse)
455
    && (mode & aux::open_mode::allow_set_file_valid_data))
456
  {
457
    LARGE_INTEGER sz;
458
    sz.QuadPart = size;
459
    if (SetFilePointerEx(m_fd, sz, nullptr, FILE_BEGIN) == FALSE)
460
      throw_ex<storage_error>(error_code(GetLastError(), system_category()), operation_t::file_seek);
461
462
    if (::SetEndOfFile(m_fd) == FALSE)
463
      throw_ex<storage_error>(error_code(GetLastError(), system_category()), operation_t::file_truncate);
464
465
#ifndef TORRENT_WINRT
466
    // if the user has permissions, avoid filling
467
    // the file with zeroes, but just fill it with
468
    // garbage instead
469
    SetFileValidData(m_fd, size);
470
#endif
471
  }
472
}
473
474
#else
475
476
file_handle::file_handle(string_view name, std::int64_t const size
477
  , open_mode_t const mode)
478
0
  : m_fd(open_file(convert_to_native_path_string(name.to_string()), mode))
479
0
{
480
#ifdef DIRECTIO_ON
481
  // for solaris
482
  if (mode & open_mode::no_cache)
483
    directio(m_fd, DIRECTIO_ON);
484
#endif
485
486
0
  if (mode & open_mode::truncate)
487
0
  {
488
0
    static_assert(sizeof(off_t) >= sizeof(size), "There seems to be a large-file issue in truncate()");
489
0
    if (ftruncate(m_fd, static_cast<off_t>(size)) < 0)
490
0
    {
491
0
      int const err = errno;
492
0
      ::close(m_fd);
493
0
      throw_ex<storage_error>(error_code(err, system_category()), operation_t::file_truncate);
494
0
    }
495
496
0
    if (!(mode & open_mode::sparse))
497
0
    {
498
0
#if TORRENT_HAS_FALLOCATE
499
      // if you get a compile error here, you might want to
500
      // define TORRENT_HAS_FALLOCATE to 0.
501
0
      int const ret = ::posix_fallocate(m_fd, 0, size);
502
      // posix_allocate fails with EINVAL in case the underlying
503
      // filesystem does not support this operation
504
0
      if (ret != 0 && ret != EINVAL && ret != ENOTSUP)
505
0
      {
506
0
        ::close(m_fd);
507
0
        throw_ex<storage_error>(error_code(ret, system_category()), operation_t::file_fallocate);
508
0
      }
509
#elif defined F_ALLOCSP64
510
      flock64 fl64;
511
      fl64.l_whence = SEEK_SET;
512
      fl64.l_start = 0;
513
      fl64.l_len = size;
514
      if (fcntl(m_fd, F_ALLOCSP64, &fl64) < 0)
515
      {
516
        int const err = errno;
517
        if (err != ENOTSUP)
518
        {
519
          ::close(m_fd);
520
          throw_ex<storage_error>(error_code(err, system_category()), operation_t::file_fallocate);
521
        }
522
      }
523
#elif defined F_PREALLOCATE
524
      fstore_t f = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, size, 0};
525
      if (fcntl(m_fd, F_PREALLOCATE, &f) < 0)
526
      {
527
        // It appears Apple's new filesystem (APFS) does not
528
        // support this control message and fails with EINVAL
529
        // if so, just skip it
530
        int const err = errno;
531
        if (err != EINVAL && err != ENOTSUP)
532
        {
533
          if (err != ENOSPC)
534
          {
535
            ::close(m_fd);
536
            throw_ex<storage_error>(error_code(err, system_category())
537
              , operation_t::file_fallocate);
538
          }
539
          // ok, let's try to allocate non contiguous space then
540
          f.fst_flags = F_ALLOCATEALL;
541
          if (fcntl(m_fd, F_PREALLOCATE, &f) < 0)
542
          {
543
            int const err2 = errno;
544
            ::close(m_fd);
545
            throw_ex<storage_error>(error_code(err2, system_category())
546
              , operation_t::file_fallocate);
547
          }
548
        }
549
      }
550
#endif // F_PREALLOCATE
551
0
    }
552
0
  }
553
554
#ifdef F_NOCACHE
555
  // for BSD/Mac
556
  if (mode & aux::open_mode::no_cache)
557
  {
558
    int yes = 1;
559
    ::fcntl(m_fd, F_NOCACHE, &yes);
560
561
#ifdef F_NODIRECT
562
    // it's OK to temporarily cache written pages
563
    ::fcntl(m_fd, F_NODIRECT, &yes);
564
#endif
565
  }
566
#endif
567
568
0
#if (TORRENT_HAS_FADVISE && defined POSIX_FADV_SEQUENTIAL)
569
0
  if (mode & aux::open_mode::sequential_access)
570
0
  {
571
    // disable read-ahead
572
0
    ::posix_fadvise(m_fd, 0, 0, POSIX_FADV_SEQUENTIAL);
573
0
  }
574
0
#endif
575
0
}
576
#endif
577
578
void file_handle::close()
579
0
{
580
0
  if (m_fd == invalid_handle) return;
581
582
#ifdef TORRENT_WINDOWS
583
584
  // if this file is open for writing, has the sparse
585
  // flag set, but there are no sparse regions, unset
586
  // the flag
587
  if ((m_open_mode & aux::open_mode::write)
588
    && (m_open_mode & aux::open_mode::sparse)
589
    && !is_sparse(m_fd))
590
  {
591
    // according to MSDN, clearing the sparse flag of a file only
592
    // works on windows vista and later
593
#ifdef TORRENT_MINGW
594
    typedef struct _FILE_SET_SPARSE_BUFFER {
595
      BOOLEAN SetSparse;
596
    } FILE_SET_SPARSE_BUFFER;
597
#endif
598
    DWORD temp;
599
    FILE_SET_SPARSE_BUFFER b;
600
    b.SetSparse = FALSE;
601
    ::DeviceIoControl(m_fd, FSCTL_SET_SPARSE, &b, sizeof(b)
602
      , nullptr, 0, &temp, nullptr);
603
  }
604
#endif
605
606
#ifdef TORRENT_WINDOWS
607
  CloseHandle(m_fd);
608
#else
609
0
  ::close(m_fd);
610
0
#endif
611
0
  m_fd = invalid_handle;
612
0
}
613
614
0
file_handle::~file_handle() { close(); }
615
616
file_handle& file_handle::operator=(file_handle&& rhs) &
617
0
{
618
0
  if (&rhs == this) return *this;
619
0
  close();
620
0
  m_fd = rhs.m_fd;
621
0
  rhs.m_fd = invalid_handle;
622
0
  return *this;
623
0
}
624
625
std::int64_t file_handle::get_size() const
626
0
{
627
#ifdef  TORRENT_WINDOWS
628
  LARGE_INTEGER file_size;
629
  if (GetFileSizeEx(fd(), &file_size) == 0)
630
    throw_ex<storage_error>(error_code(GetLastError(), system_category()), operation_t::file_stat);
631
  return file_size.QuadPart;
632
#else
633
0
  struct ::stat fs;
634
0
  if (::fstat(fd(), &fs) != 0)
635
0
    throw_ex<storage_error>(error_code(errno, system_category()), operation_t::file_stat);
636
0
  return fs.st_size;
637
0
#endif
638
0
}
639
640
} // namespace aux
641
642
}