Coverage Report

Created: 2026-02-25 06:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libtorrent/src/path.cpp
Line
Count
Source
1
/*
2
3
Copyright (c) 2017-2019, Alden Torres
4
Copyright (c) 2017-2021, Arvid Norberg
5
Copyright (c) 2017, Steven Siloti
6
Copyright (c) 2020, Kacper Michajłow
7
Copyright (c) 2020, Tiger Wang
8
All rights reserved.
9
10
Redistribution and use in source and binary forms, with or without
11
modification, are permitted provided that the following conditions
12
are met:
13
14
    * Redistributions of source code must retain the above copyright
15
      notice, this list of conditions and the following disclaimer.
16
    * Redistributions in binary form must reproduce the above copyright
17
      notice, this list of conditions and the following disclaimer in
18
      the documentation and/or other materials provided with the distribution.
19
    * Neither the name of the author nor the names of its
20
      contributors may be used to endorse or promote products derived
21
      from this software without specific prior written permission.
22
23
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
27
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33
POSSIBILITY OF SUCH DAMAGE.
34
35
*/
36
37
#include "libtorrent/aux_/disable_warnings_push.hpp"
38
39
#ifdef __GNUC__
40
#pragma GCC diagnostic push
41
#pragma GCC diagnostic ignored "-Wunused-macros"
42
#endif
43
44
#ifdef __clang__
45
#pragma clang diagnostic push
46
#pragma clang diagnostic ignored "-Wunknown-pragmas"
47
#pragma clang diagnostic ignored "-Wunused-macros"
48
#pragma clang diagnostic ignored "-Wreserved-id-macro"
49
#endif
50
51
// on mingw this is necessary to enable 64-bit time_t, specifically used for
52
// the stat struct. Without this, modification times returned by stat may be
53
// incorrect and consistently fail resume data
54
#ifndef __MINGW_USE_VC2005_COMPAT
55
# define __MINGW_USE_VC2005_COMPAT
56
#endif
57
58
#ifdef __clang__
59
#pragma clang diagnostic pop
60
#endif
61
62
#ifdef __GNUC__
63
#pragma GCC diagnostic pop
64
#endif
65
66
#include "libtorrent/aux_/disable_warnings_pop.hpp"
67
68
#include "libtorrent/config.hpp"
69
#include "libtorrent/aux_/alloca.hpp"
70
#include "libtorrent/aux_/path.hpp"
71
#include "libtorrent/aux_/directory.hpp"
72
#include "libtorrent/string_util.hpp"
73
#include <cstring>
74
75
#include "libtorrent/aux_/escape_string.hpp" // for convert_to_native
76
#include "libtorrent/assert.hpp"
77
#include "libtorrent/aux_/throw.hpp"
78
79
#include "libtorrent/aux_/disable_warnings_push.hpp"
80
81
#include <sys/stat.h>
82
#include <climits> // for IOV_MAX
83
84
#ifdef TORRENT_WINDOWS
85
// windows part
86
87
#include "libtorrent/utf8.hpp"
88
#include "libtorrent/aux_/win_util.hpp"
89
90
#include "libtorrent/aux_/windows.hpp"
91
#include <winioctl.h>
92
#ifndef TORRENT_MINGW
93
#include <direct.h> // for _getcwd, _mkdir
94
#else
95
#include <dirent.h>
96
#endif
97
#include <sys/types.h>
98
#else
99
// posix part
100
101
#include <unistd.h>
102
#include <sys/types.h>
103
#include <cerrno>
104
#include <dirent.h>
105
#include <sys/ioctl.h>
106
107
#endif // posix part
108
109
#include "libtorrent/aux_/disable_warnings_pop.hpp"
110
#include "libtorrent/aux_/storage_utils.hpp" // copy_file
111
112
namespace libtorrent {
113
114
#if defined TORRENT_WINDOWS
115
  std::string convert_from_native_path(wchar_t const* s)
116
  {
117
    if (s[0] == L'\\' && s[1] == L'\\' && s[2] == L'?' && s[3] == L'\\') s += 4;
118
    return convert_from_wstring(s);
119
  }
120
#else
121
0
  std::string convert_from_native_path(char const* s) { return convert_from_native(s); }
122
#endif
123
124
namespace {
125
  struct free_function
126
  {
127
0
    void operator()(void* ptr) const noexcept { std::free(ptr); }
128
  };
129
130
  template <typename T>
131
  std::unique_ptr<T, free_function> make_free_holder(T* ptr)
132
0
  {
133
0
    return std::unique_ptr<T, free_function>(ptr, free_function{});
134
0
  }
135
136
#ifdef TORRENT_WINDOWS
137
  time_t file_time_to_posix(FILETIME f)
138
  {
139
    const std::uint64_t posix_time_offset = 11644473600LL;
140
    std::uint64_t ft = (std::uint64_t(f.dwHighDateTime) << 32)
141
      | f.dwLowDateTime;
142
143
    // windows filetime is specified in 100 nanoseconds resolution.
144
    // convert to seconds
145
    return time_t(ft / 10000000 - posix_time_offset);
146
  }
147
148
  void fill_file_status(file_status & s, LARGE_INTEGER file_size, DWORD file_attributes, FILETIME creation_time, FILETIME last_access, FILETIME last_write)
149
  {
150
    s.file_size = file_size.QuadPart;
151
    s.ctime = file_time_to_posix(creation_time);
152
    s.atime = file_time_to_posix(last_access);
153
    s.mtime = file_time_to_posix(last_write);
154
155
    s.mode = (file_attributes & FILE_ATTRIBUTE_DIRECTORY)
156
      ? file_status::directory
157
      : (file_attributes & FILE_ATTRIBUTE_DEVICE)
158
      ? file_status::character_special : file_status::regular_file;
159
  }
160
161
  void fill_file_status(file_status & s, DWORD file_size_low, DWORD file_size_high, DWORD file_attributes, FILETIME creation_time, FILETIME last_access, FILETIME last_write)
162
  {
163
    LARGE_INTEGER file_size;
164
    file_size.HighPart = static_cast<LONG>(file_size_high);
165
    file_size.LowPart = file_size_low;
166
167
    fill_file_status(s, file_size, file_attributes, creation_time, last_access, last_write);
168
  }
169
170
#ifdef TORRENT_WINRT
171
  FILETIME to_file_time(LARGE_INTEGER i)
172
  {
173
    FILETIME time;
174
    time.dwHighDateTime = i.HighPart;
175
    time.dwLowDateTime = i.LowPart;
176
177
    return time;
178
  }
179
180
  void fill_file_status(file_status & s, LARGE_INTEGER file_size, DWORD file_attributes, LARGE_INTEGER creation_time, LARGE_INTEGER last_access, LARGE_INTEGER last_write)
181
  {
182
    fill_file_status(s, file_size, file_attributes, to_file_time(creation_time), to_file_time(last_access), to_file_time(last_write));
183
  }
184
#endif
185
#endif
186
} // anonymous namespace
187
188
  native_path_string convert_to_native_path_string(std::string const& path)
189
0
  {
190
#ifdef TORRENT_WINDOWS
191
#if TORRENT_USE_UNC_PATHS
192
    // UNC paths must be absolute
193
    // network paths are already UNC paths
194
    std::string prepared_path = complete(path);
195
    if (prepared_path.substr(0,2) != "\\\\")
196
      prepared_path = "\\\\?\\" + prepared_path;
197
    std::replace(prepared_path.begin(), prepared_path.end(), '/', '\\');
198
199
    return convert_to_wstring(prepared_path);
200
#else
201
    return convert_to_wstring(path);
202
#endif
203
#else // TORRENT_WINDOWS
204
0
    return convert_to_native(path);
205
0
#endif
206
0
  }
207
208
  void stat_file(std::string const& inf, file_status* s
209
    , error_code& ec, int const flags)
210
0
  {
211
0
    ec.clear();
212
0
    native_path_string f = convert_to_native_path_string(inf);
213
#ifdef TORRENT_WINDOWS
214
215
    WIN32_FILE_ATTRIBUTE_DATA data;
216
    if (!GetFileAttributesExW(f.c_str(), GetFileExInfoStandard, &data))
217
    {
218
      ec.assign(GetLastError(), system_category());
219
      TORRENT_ASSERT(ec);
220
      return;
221
    }
222
223
    // Fallback to GetFileInformationByHandle for symlinks
224
    if (!(flags & dont_follow_links) && (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
225
    {
226
#ifdef TORRENT_WINRT
227
228
      CREATEFILE2_EXTENDED_PARAMETERS Extended
229
      {
230
        sizeof(CREATEFILE2_EXTENDED_PARAMETERS),
231
        0, // no file attributes
232
        FILE_FLAG_BACKUP_SEMANTICS // in order to open a directory, we need the FILE_FLAG_BACKUP_SEMANTICS
233
      };
234
235
      const auto h = CreateFile2(f.c_str(), 0, FILE_SHARE_DELETE | FILE_SHARE_READ
236
        | FILE_SHARE_WRITE, OPEN_EXISTING, &Extended);
237
238
      if (h == INVALID_HANDLE_VALUE)
239
      {
240
        ec.assign(GetLastError(), system_category());
241
        TORRENT_ASSERT(ec);
242
        return;
243
      }
244
245
      FILE_BASIC_INFO Basic;
246
      FILE_STANDARD_INFO Standard;
247
248
      if (
249
        !GetFileInformationByHandleEx(h, FILE_INFO_BY_HANDLE_CLASS::FileBasicInfo, &Basic, sizeof(FILE_BASIC_INFO)) ||
250
        !GetFileInformationByHandleEx(h, FILE_INFO_BY_HANDLE_CLASS::FileStandardInfo, &Standard, sizeof(FILE_STANDARD_INFO))
251
      )
252
      {
253
        ec.assign(GetLastError(), system_category());
254
        TORRENT_ASSERT(ec);
255
        CloseHandle(h);
256
        return;
257
      }
258
      CloseHandle(h);
259
260
      fill_file_status(*s, Standard.EndOfFile, Basic.FileAttributes, Basic.CreationTime, Basic.LastAccessTime, Basic.LastWriteTime);
261
      return;
262
263
#else
264
265
      // in order to open a directory, we need the FILE_FLAG_BACKUP_SEMANTICS
266
      HANDLE h = CreateFileW(f.c_str(), 0, FILE_SHARE_DELETE | FILE_SHARE_READ
267
        | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
268
      if (h == INVALID_HANDLE_VALUE)
269
      {
270
        ec.assign(GetLastError(), system_category());
271
        TORRENT_ASSERT(ec);
272
        return;
273
      }
274
275
      BY_HANDLE_FILE_INFORMATION handle_data;
276
      if (!GetFileInformationByHandle(h, &handle_data))
277
      {
278
        ec.assign(GetLastError(), system_category());
279
        TORRENT_ASSERT(ec);
280
        CloseHandle(h);
281
        return;
282
      }
283
      CloseHandle(h);
284
285
      fill_file_status(*s, handle_data.nFileSizeLow, handle_data.nFileSizeHigh, handle_data.dwFileAttributes, handle_data.ftCreationTime, handle_data.ftLastAccessTime, handle_data.ftLastWriteTime);
286
      return;
287
288
#endif
289
    }
290
291
    fill_file_status(*s, data.nFileSizeLow, data.nFileSizeHigh, data.dwFileAttributes, data.ftCreationTime, data.ftLastAccessTime, data.ftLastWriteTime);
292
293
#else
294
295
    // posix version
296
297
0
    struct ::stat ret{};
298
0
    int retval;
299
0
    if (flags & dont_follow_links)
300
0
      retval = ::lstat(f.c_str(), &ret);
301
0
    else
302
0
      retval = ::stat(f.c_str(), &ret);
303
0
    if (retval < 0)
304
0
    {
305
0
      ec.assign(errno, system_category());
306
0
      return;
307
0
    }
308
309
    // make sure the _FILE_OFFSET_BITS define worked
310
    // on this platform. It's supposed to make file
311
    // related functions support 64-bit offsets.
312
0
    static_assert(sizeof(ret.st_size) >= 8, "64 bit file operations are required");
313
314
0
    s->file_size = ret.st_size;
315
0
    s->atime = std::uint64_t(ret.st_atime);
316
0
    s->mtime = std::uint64_t(ret.st_mtime);
317
0
    s->ctime = std::uint64_t(ret.st_ctime);
318
319
0
    s->mode = (S_ISREG(ret.st_mode) ? file_status::regular_file : 0)
320
0
      | (S_ISDIR(ret.st_mode) ? file_status::directory : 0)
321
0
      | (S_ISLNK(ret.st_mode) ? file_status::link : 0)
322
0
      | (S_ISFIFO(ret.st_mode) ? file_status::fifo : 0)
323
0
      | (S_ISCHR(ret.st_mode) ? file_status::character_special : 0)
324
0
      | (S_ISBLK(ret.st_mode) ? file_status::block_special : 0)
325
0
      | (S_ISSOCK(ret.st_mode) ? file_status::socket : 0);
326
327
0
#endif // TORRENT_WINDOWS
328
0
  }
329
330
  void rename(std::string const& inf, std::string const& newf, error_code& ec)
331
0
  {
332
0
    ec.clear();
333
334
0
    native_path_string f1 = convert_to_native_path_string(inf);
335
0
    native_path_string f2 = convert_to_native_path_string(newf);
336
337
0
    if (f1 == f2) return;
338
339
#if defined TORRENT_WINDOWS
340
#define RenameFunction_ ::_wrename
341
#else
342
0
#define RenameFunction_ ::rename
343
0
#endif
344
345
0
    if (RenameFunction_(f1.c_str(), f2.c_str()) < 0)
346
0
    {
347
0
      ec.assign(errno, generic_category());
348
0
    }
349
0
#undef RenameFunction_
350
0
  }
351
352
  void create_directories(std::string const& f, error_code& ec)
353
0
  {
354
0
    ec.clear();
355
0
    if (is_directory(f, ec)) return;
356
0
    if (ec != boost::system::errc::no_such_file_or_directory)
357
0
      return;
358
0
    ec.clear();
359
0
    if (is_root_path(f))
360
0
    {
361
      // this is just to set ec correctly, in case this root path isn't
362
      // mounted
363
0
      file_status s;
364
0
      stat_file(f, &s, ec);
365
0
      return;
366
0
    }
367
0
    if (has_parent_path(f))
368
0
    {
369
0
      create_directories(parent_path(f), ec);
370
0
      if (ec) return;
371
0
    }
372
0
    create_directory(f, ec);
373
0
  }
374
375
  void create_directory(std::string const& f, error_code& ec)
376
0
  {
377
0
    ec.clear();
378
379
0
    native_path_string n = convert_to_native_path_string(f);
380
#ifdef TORRENT_WINDOWS
381
    if (CreateDirectoryW(n.c_str(), nullptr) == 0
382
      && GetLastError() != ERROR_ALREADY_EXISTS)
383
      ec.assign(GetLastError(), system_category());
384
#else
385
0
    int ret = ::mkdir(n.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
386
0
    if (ret < 0 && errno != EEXIST)
387
0
      ec.assign(errno, system_category());
388
0
#endif
389
0
  }
390
391
  void hard_link(std::string const& file, std::string const& link
392
    , error_code& ec)
393
0
  {
394
0
    native_path_string n_exist = convert_to_native_path_string(file);
395
0
    native_path_string n_link = convert_to_native_path_string(link);
396
#ifdef TORRENT_WINDOWS
397
#ifndef TORRENT_WINRT
398
399
    BOOL ret = CreateHardLinkW(n_link.c_str(), n_exist.c_str(), nullptr);
400
    if (ret)
401
    {
402
      ec.clear();
403
      return;
404
    }
405
    // something failed. Does the filesystem not support hard links?
406
    DWORD const error = GetLastError();
407
    if (error != ERROR_INVALID_FUNCTION)
408
    {
409
      // it's possible CreateHardLink will copy the file internally too,
410
      // if the filesystem does not support it.
411
      ec.assign(GetLastError(), system_category());
412
      return;
413
    }
414
415
    // fall back to making a copy
416
#endif
417
#else
418
    // assume posix's link() function exists
419
0
    int ret = ::link(n_exist.c_str(), n_link.c_str());
420
421
0
    if (ret == 0)
422
0
    {
423
0
      ec.clear();
424
0
      return;
425
0
    }
426
427
    // most errors are passed through, except for the ones that indicate that
428
    // hard links are not supported and require a copy.
429
    // TODO: 2 test this on a FAT volume to see what error we get!
430
0
    if (errno != EMLINK
431
0
      && errno != EXDEV
432
#ifdef TORRENT_BEOS
433
      // haiku returns EPERM when the filesystem doesn't support hard link
434
      && errno != EPERM
435
#endif
436
0
      )
437
0
    {
438
      // some error happened, report up to the caller
439
0
      ec.assign(errno, system_category());
440
0
      return;
441
0
    }
442
443
    // fall back to making a copy
444
445
0
#endif
446
447
    // if we get here, we should copy the file
448
0
    storage_error se;
449
0
    aux::copy_file(file, link, se);
450
0
    ec = se.ec;
451
0
  }
452
453
  bool is_directory(std::string const& f, error_code& ec)
454
0
  {
455
0
    ec.clear();
456
0
    error_code e;
457
0
    file_status s;
458
0
    stat_file(f, &s, e);
459
0
    if (!e && s.mode & file_status::directory) return true;
460
0
    ec = e;
461
0
    return false;
462
0
  }
463
464
  std::string extension(std::string const& f)
465
0
  {
466
0
    for (int i = int(f.size()) - 1; i >= 0; --i)
467
0
    {
468
0
      auto const idx = static_cast<std::size_t>(i);
469
0
      if (f[idx] == '/') break;
470
#ifdef TORRENT_WINDOWS
471
      if (f[idx] == '\\') break;
472
#endif
473
0
      if (f[idx] != '.') continue;
474
0
      return f.substr(idx);
475
0
    }
476
0
    return "";
477
0
  }
478
479
  std::string remove_extension(std::string const& f)
480
0
  {
481
0
    char const* slash = std::strrchr(f.c_str(), '/');
482
#ifdef TORRENT_WINDOWS
483
    slash = std::max((char const*)std::strrchr(f.c_str(), '\\'), slash);
484
#endif
485
0
    char const* ext = std::strrchr(f.c_str(), '.');
486
    // if we don't have an extension, just return f
487
0
    if (ext == nullptr || ext == &f[0] || (slash != nullptr && ext < slash)) return f;
488
0
    return f.substr(0, aux::numeric_cast<std::size_t>(ext - &f[0]));
489
0
  }
490
491
  bool is_root_path(std::string const& f)
492
0
  {
493
0
    if (f.empty()) return false;
494
495
#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
496
    // match \\ form
497
    if (f == "\\\\") return true;
498
    int i = 0;
499
    // match the xx:\ or xx:/ form
500
    while (f[i] && is_alpha(f[i])) ++i;
501
    if (i == int(f.size()-2) && f[i] == ':' && (f[i+1] == '\\' || f[i+1] == '/'))
502
      return true;
503
    // match network paths \\computer_name\ form
504
    if (f.size() > 2 && f[0] == '\\' && f[1] == '\\')
505
    {
506
      // we don't care about the last character, since it's OK for it
507
      // to be a slash or a back slash
508
      bool found = false;
509
      for (int j = 2; j < int(f.size()) - 1; ++j)
510
      {
511
        if (f[j] != '\\' && f[j] != '/') continue;
512
        // there is a directory separator in here,
513
        // i.e. this is not the root
514
        found = true;
515
        break;
516
      }
517
      if (!found) return true;
518
    }
519
#else
520
    // as well as parent_path("/") should be "/".
521
0
    if (f == "/") return true;
522
0
#endif
523
0
    return false;
524
0
  }
525
526
  bool path_equal(std::string const& lhs, std::string const& rhs)
527
0
  {
528
0
    std::string::size_type const lhs_size = !lhs.empty()
529
0
      && (lhs[lhs.size()-1] == '/'
530
#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
531
      || lhs[lhs.size()-1] == '\\'
532
#endif
533
0
      ) ? lhs.size() - 1 : lhs.size();
534
535
0
    std::string::size_type const rhs_size = !rhs.empty()
536
0
      && (rhs[rhs.size()-1] == '/'
537
#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
538
      || rhs[rhs.size()-1] == '\\'
539
#endif
540
0
      ) ? rhs.size() - 1 : rhs.size();
541
0
    return lhs.compare(0, lhs_size, rhs, 0, rhs_size) == 0;
542
0
  }
543
544
  // <0: lhs < rhs
545
  //  0: lhs == rhs
546
  // >0: lhs > rhs
547
  int path_compare(string_view const lhs, string_view const lfile
548
    , string_view const rhs, string_view const rfile)
549
0
  {
550
0
    for (auto lhs_elems = lsplit_path(lhs), rhs_elems = lsplit_path(rhs);
551
0
      !lhs_elems.first.empty() || !rhs_elems.first.empty();
552
0
      lhs_elems = lsplit_path(lhs_elems.second), rhs_elems = lsplit_path(rhs_elems.second))
553
0
    {
554
0
      if (lhs_elems.first.empty() || rhs_elems.first.empty())
555
0
      {
556
0
        if (lhs_elems.first.empty()) lhs_elems.first = lfile;
557
0
        if (rhs_elems.first.empty()) rhs_elems.first = rfile;
558
0
        return lhs_elems.first.compare(rhs_elems.first);
559
0
      }
560
561
0
      int const ret = lhs_elems.first.compare(rhs_elems.first);
562
0
      if (ret != 0) return ret;
563
0
    }
564
0
    return 0;
565
0
  }
566
567
  bool has_parent_path(std::string const& f)
568
0
  {
569
0
    if (f.empty()) return false;
570
0
    if (is_root_path(f)) return false;
571
572
0
    int len = int(f.size()) - 1;
573
    // if the last character is / or \ ignore it
574
0
    if (f[std::size_t(len)] == '/' || f[std::size_t(len)] == '\\') --len;
575
0
    while (len >= 0)
576
0
    {
577
0
      if (f[std::size_t(len)] == '/' || f[std::size_t(len)] == '\\')
578
0
        break;
579
0
      --len;
580
0
    }
581
582
0
    return len >= 0;
583
0
  }
584
585
  std::string parent_path(std::string const& f)
586
0
  {
587
0
    if (f.empty()) return f;
588
589
    #ifdef TORRENT_WINDOWS
590
    if (f == "\\\\") return "";
591
    #endif
592
0
    if (f == "/") return "";
593
594
0
    int len = int(f.size());
595
    // if the last character is / or \ ignore it
596
0
    if (f[std::size_t(len - 1)] == '/' || f[std::size_t(len - 1)] == '\\') --len;
597
0
    while (len > 0)
598
0
    {
599
0
      --len;
600
      #ifdef TORRENT_WINDOWS
601
      if (f[std::size_t(len)] == '/' || f[std::size_t(len)] == '\\') break;
602
      #else
603
0
      if (f[std::size_t(len)] == '/') break;  
604
0
      #endif    
605
0
    }
606
607
0
    if (f[std::size_t(len)] == '/' || f[std::size_t(len)] == '\\') ++len;
608
0
    return std::string(f.c_str(), std::size_t(len));
609
0
  }
610
611
  std::string filename(std::string const& f)
612
0
  {
613
0
    if (f.empty()) return "";
614
0
    char const* first = f.c_str();
615
0
    char const* sep = std::strrchr(first, '/');
616
#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
617
    char const* altsep = std::strrchr(first, '\\');
618
    if (sep == nullptr || altsep > sep) sep = altsep;
619
#endif
620
0
    if (sep == nullptr) return f;
621
622
0
    if (sep - first == int(f.size()) - 1)
623
0
    {
624
      // if the last character is a / (or \)
625
      // ignore it
626
0
      int len = 0;
627
0
      while (sep > first)
628
0
      {
629
0
        --sep;
630
0
        if (*sep == '/'
631
#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
632
          || *sep == '\\'
633
#endif
634
0
          )
635
0
          return std::string(sep + 1, std::size_t(len));
636
0
        ++len;
637
0
      }
638
0
      return std::string(first, std::size_t(len));
639
640
0
    }
641
0
    return std::string(sep + 1);
642
0
  }
643
644
  void append_path(std::string& branch, string_view leaf)
645
0
  {
646
0
    TORRENT_ASSERT(!is_complete(leaf));
647
0
    if (branch.empty() || branch == ".")
648
0
    {
649
0
      branch.assign(leaf.data(), leaf.size());
650
0
      return;
651
0
    }
652
0
    if (leaf.empty()) return;
653
654
#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
655
#define TORRENT_SEPARATOR_CHAR '\\'
656
    bool const need_sep = branch[branch.size()-1] != '\\'
657
      && branch[branch.size()-1] != '/';
658
#else
659
0
#define TORRENT_SEPARATOR_CHAR '/'
660
0
    bool const need_sep = branch[branch.size()-1] != '/';
661
0
#endif
662
663
0
    if (need_sep) branch += TORRENT_SEPARATOR_CHAR;
664
0
    branch.append(leaf.data(), leaf.size());
665
0
  }
666
667
  std::string combine_path(string_view lhs, string_view rhs)
668
0
  {
669
0
    TORRENT_ASSERT(!is_complete(rhs));
670
0
    if (lhs.empty() || lhs == ".") return rhs.to_string();
671
0
    if (rhs.empty() || rhs == ".") return lhs.to_string();
672
673
#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
674
#define TORRENT_SEPARATOR "\\"
675
    bool const need_sep = lhs[lhs.size() - 1] != '\\' && lhs[lhs.size() - 1] != '/';
676
#else
677
0
#define TORRENT_SEPARATOR "/"
678
0
    bool const need_sep = lhs[lhs.size() - 1] != '/';
679
0
#endif
680
0
    std::string ret;
681
0
    std::size_t target_size = lhs.size() + rhs.size() + 2;
682
0
    ret.resize(target_size);
683
0
    target_size = aux::numeric_cast<std::size_t>(std::snprintf(&ret[0], target_size, "%*s%s%*s"
684
0
      , int(lhs.size()), lhs.data()
685
0
      , (need_sep ? TORRENT_SEPARATOR : "")
686
0
      , int(rhs.size()), rhs.data()));
687
0
    ret.resize(target_size);
688
0
    return ret;
689
0
  }
690
691
  std::string lexically_relative(string_view base, string_view target)
692
0
  {
693
    // first, strip trailing directory separators
694
0
    if (!base.empty() && base.back() == TORRENT_SEPARATOR_CHAR)
695
0
      base.remove_suffix(1);
696
0
    if (!target.empty() && target.back() == TORRENT_SEPARATOR_CHAR)
697
0
      target.remove_suffix(1);
698
699
    // strip common path elements
700
0
    for (;;)
701
0
    {
702
0
      if (base.empty()) break;
703
0
      string_view prev_base = base;
704
0
      string_view prev_target = target;
705
706
0
      string_view base_element;
707
0
      string_view target_element;
708
0
      std::tie(base_element, base) = split_string(base, TORRENT_SEPARATOR_CHAR);
709
0
      std::tie(target_element, target) = split_string(target, TORRENT_SEPARATOR_CHAR);
710
0
      if (base_element == target_element) continue;
711
712
0
      base = prev_base;
713
0
      target = prev_target;
714
0
      break;
715
0
    }
716
717
    // count number of path elements left in base, and prepend that number of
718
    // "../" to target
719
720
    // base always points to a directory. There's an implied directory
721
    // separator at the end of it
722
0
    int const num_steps = static_cast<int>(std::count(
723
0
      base.begin(), base.end(), TORRENT_SEPARATOR_CHAR)) + (base.empty() ? 0 : 1);
724
0
    std::string ret;
725
0
    for (int i = 0; i < num_steps; ++i)
726
0
      ret += ".." TORRENT_SEPARATOR;
727
728
0
    ret += target.to_string();
729
0
    return ret;
730
0
  }
731
732
  std::string current_working_directory()
733
0
  {
734
#if defined TORRENT_WINDOWS
735
#define GetCurrentDir_ ::_wgetcwd
736
#else
737
0
#define GetCurrentDir_ ::getcwd
738
0
#endif
739
0
    auto cwd = GetCurrentDir_(nullptr, 0);
740
0
    if (cwd == nullptr)
741
0
      aux::throw_ex<system_error>(error_code(errno, generic_category()));
742
0
    auto holder = make_free_holder(cwd);
743
0
    return convert_from_native_path(cwd);
744
0
#undef GetCurrentDir_
745
0
  }
746
747
#if TORRENT_USE_UNC_PATHS
748
  std::string canonicalize_path(string_view f)
749
  {
750
    std::string ret;
751
    ret.resize(f.size());
752
    char* write_cur = &ret[0];
753
    char* last_write_sep = write_cur;
754
755
    char const* read_cur = f.data();
756
    char const* last_read_sep = read_cur;
757
758
    // the last_*_sep pointers point to one past
759
    // the last path separator encountered and is
760
    // initialized to the first character in the path
761
    for (int i = 0; i < int(f.size()); ++i)
762
    {
763
      if (*read_cur != '\\')
764
      {
765
        *write_cur++ = *read_cur++;
766
        continue;
767
      }
768
      int element_len = int(read_cur - last_read_sep);
769
      if (element_len == 1 && std::memcmp(last_read_sep, ".", 1) == 0)
770
      {
771
        --write_cur;
772
        ++read_cur;
773
        last_read_sep = read_cur;
774
        continue;
775
      }
776
      if (element_len == 2 && std::memcmp(last_read_sep, "..", 2) == 0)
777
      {
778
        // find the previous path separator
779
        if (last_write_sep > &ret[0])
780
        {
781
          --last_write_sep;
782
          while (last_write_sep > &ret[0]
783
            && last_write_sep[-1] != '\\')
784
            --last_write_sep;
785
        }
786
        write_cur = last_write_sep;
787
        // find the previous path separator
788
        if (last_write_sep > &ret[0])
789
        {
790
          --last_write_sep;
791
          while (last_write_sep > &ret[0]
792
            && last_write_sep[-1] != '\\')
793
            --last_write_sep;
794
        }
795
        ++read_cur;
796
        last_read_sep = read_cur;
797
        continue;
798
      }
799
      *write_cur++ = *read_cur++;
800
      last_write_sep = write_cur;
801
      last_read_sep = read_cur;
802
    }
803
    // terminate destination string
804
    *write_cur = 0;
805
    ret.resize(write_cur - &ret[0]);
806
    return ret;
807
  }
808
#endif
809
  bool exists(std::string const& f, error_code& ec)
810
0
  {
811
0
    file_status s;
812
0
    stat_file(f, &s, ec);
813
0
    if (ec)
814
0
    {
815
      // if the filename is too long, the file also cannot exist
816
0
      if (ec == boost::system::errc::no_such_file_or_directory
817
0
        || ec == boost::system::errc::filename_too_long)
818
0
        ec.clear();
819
0
      return false;
820
0
    }
821
0
    return true;
822
0
  }
823
824
  void remove(std::string const& inf, error_code& ec)
825
0
  {
826
0
    ec.clear();
827
0
    native_path_string f = convert_to_native_path_string(inf);
828
829
#ifdef TORRENT_WINDOWS
830
    // windows does not allow trailing / or \ in
831
    // the path when removing files
832
    while (!f.empty() && (
833
      f.back() == '/' ||
834
      f.back() == '\\'
835
      )) f.pop_back();
836
837
    if (DeleteFileW(f.c_str()) == 0)
838
    {
839
      if (GetLastError() == ERROR_ACCESS_DENIED)
840
      {
841
        if (RemoveDirectoryW(f.c_str()) != 0)
842
          return;
843
      }
844
      ec.assign(GetLastError(), system_category());
845
      return;
846
    }
847
#else // TORRENT_WINDOWS
848
0
    if (::remove(f.c_str()) < 0)
849
0
    {
850
0
      ec.assign(errno, system_category());
851
0
      return;
852
0
    }
853
0
#endif // TORRENT_WINDOWS
854
0
  }
855
856
  void remove_all(std::string const& f, error_code& ec)
857
0
  {
858
0
    ec.clear();
859
860
0
    file_status s;
861
0
    stat_file(f, &s, ec);
862
0
    if (ec) return;
863
864
0
    if (s.mode & file_status::directory)
865
0
    {
866
0
      for (aux::directory i(f, ec); !i.done(); i.next(ec))
867
0
      {
868
0
        if (ec) return;
869
0
        std::string p = i.file();
870
0
        if (p == "." || p == "..") continue;
871
0
        remove_all(combine_path(f, p), ec);
872
0
        if (ec) return;
873
0
      }
874
0
    }
875
0
    remove(f, ec);
876
0
  }
877
878
  std::pair<string_view, string_view> rsplit_path(string_view p)
879
0
  {
880
0
    if (p.empty()) return {{}, {}};
881
0
    if (p.back() == TORRENT_SEPARATOR_CHAR) p.remove_suffix(1);
882
#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
883
    else if (p.back() == '/') p.remove_suffix(1);
884
#endif
885
#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
886
    auto const sep = p.find_last_of("/\\");
887
#else
888
0
    auto const sep = p.find_last_of(TORRENT_SEPARATOR_CHAR);
889
0
#endif
890
0
    if (sep == string_view::npos) return {{}, p};
891
0
    return { p.substr(0, sep), p.substr(sep + 1) };
892
0
  }
893
894
  std::pair<string_view, string_view> lsplit_path(string_view p)
895
0
  {
896
0
    if (p.empty()) return {{}, {}};
897
    // for absolute paths, skip the initial "/"
898
0
    if (p.front() == TORRENT_SEPARATOR_CHAR) p.remove_prefix(1);
899
#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
900
    else if (p.front() == '/') p.remove_prefix(1);
901
#endif
902
#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
903
    auto const sep = p.find_first_of("/\\");
904
#else
905
0
    auto const sep = p.find_first_of(TORRENT_SEPARATOR_CHAR);
906
0
#endif
907
0
    if (sep == string_view::npos) return {p, {}};
908
0
    return { p.substr(0, sep), p.substr(sep + 1) };
909
0
  }
910
911
  std::pair<string_view, string_view> lsplit_path(string_view p, std::size_t pos)
912
0
  {
913
0
    if (p.empty()) return {{}, {}};
914
    // for absolute paths, skip the initial "/"
915
0
    if (p.front() == TORRENT_SEPARATOR_CHAR
916
#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
917
      || p.front() == '/'
918
#endif
919
0
    )
920
0
    { p.remove_prefix(1); if (pos > 0) --pos; }
921
#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
922
    auto const sep = find_first_of(p, "/\\", std::string::size_type(pos));
923
#else
924
0
    auto const sep = find_first_of(p, TORRENT_SEPARATOR_CHAR, std::string::size_type(pos));
925
0
#endif
926
0
    if (sep == string_view::npos) return {p, {}};
927
0
    return { p.substr(0, sep), p.substr(sep + 1) };
928
0
  }
929
930
  std::string complete(string_view f)
931
0
  {
932
0
    if (is_complete(f)) return f.to_string();
933
0
    auto parts = lsplit_path(f);
934
0
    if (parts.first == ".") f = parts.second;
935
0
    return combine_path(current_working_directory(), f);
936
0
  }
937
938
  bool is_complete(string_view f)
939
0
  {
940
0
    if (f.empty()) return false;
941
#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
942
    int i = 0;
943
    // match the xx:\ or xx:/ form
944
    while (f[i] && is_alpha(f[i])) ++i;
945
    if (i < int(f.size()-1) && f[i] == ':' && (f[i+1] == '\\' || f[i+1] == '/'))
946
      return true;
947
948
    // match the \\ form
949
    if (int(f.size()) >= 2 && f[0] == '\\' && f[1] == '\\')
950
      return true;
951
    return false;
952
#else
953
0
    if (f[0] == '/') return true;
954
0
    return false;
955
0
#endif
956
0
  }
957
}