aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/libs/cxxsupp/libcxxmsvc/src/filesystem/posix_compat.h
blob: 36116ec5a39526370929bab7e3457a908f1e2c67 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

//
// POSIX-like portability helper functions.
//
// These generally behave like the proper posix functions, with these
// exceptions:
// On Windows, they take paths in wchar_t* form, instead of char* form.
// The symlink() function is split into two frontends, symlink_file()
// and symlink_dir().
//
// These are provided within an anonymous namespace within the detail
// namespace - callers need to include this header and call them as
// detail::function(), regardless of platform.
//

#ifndef POSIX_COMPAT_H
#define POSIX_COMPAT_H

#include <__assert>
#include <filesystem>

#include "filesystem_common.h"

#if defined(_LIBCPP_WIN32API)
# define WIN32_LEAN_AND_MEAN
# define NOMINMAX
# include <windows.h>
# include <io.h>
# include <winioctl.h>
#else
# include <unistd.h>
# include <sys/stat.h>
# include <sys/statvfs.h>
#endif
#include <time.h>

#if defined(_LIBCPP_WIN32API)
// This struct isn't defined in the normal Windows SDK, but only in the
// Windows Driver Kit.
struct LIBCPP_REPARSE_DATA_BUFFER {
  unsigned long  ReparseTag;
  unsigned short ReparseDataLength;
  unsigned short Reserved;
  union {
    struct {
      unsigned short SubstituteNameOffset;
      unsigned short SubstituteNameLength;
      unsigned short PrintNameOffset;
      unsigned short PrintNameLength;
      unsigned long  Flags;
      wchar_t        PathBuffer[1];
    } SymbolicLinkReparseBuffer;
    struct {
      unsigned short SubstituteNameOffset;
      unsigned short SubstituteNameLength;
      unsigned short PrintNameOffset;
      unsigned short PrintNameLength;
      wchar_t        PathBuffer[1];
    } MountPointReparseBuffer;
    struct {
      unsigned char DataBuffer[1];
    } GenericReparseBuffer;
  };
};
#endif

_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM

namespace detail {
namespace {

#if defined(_LIBCPP_WIN32API)

// Various C runtime header sets provide more or less of these. As we
// provide our own implementation, undef all potential defines from the
// C runtime headers and provide a complete set of macros of our own.

#undef _S_IFMT
#undef _S_IFDIR
#undef _S_IFCHR
#undef _S_IFIFO
#undef _S_IFREG
#undef _S_IFBLK
#undef _S_IFLNK
#undef _S_IFSOCK

#define _S_IFMT   0xF000
#define _S_IFDIR  0x4000
#define _S_IFCHR  0x2000
#define _S_IFIFO  0x1000
#define _S_IFREG  0x8000
#define _S_IFBLK  0x6000
#define _S_IFLNK  0xA000
#define _S_IFSOCK 0xC000

#undef S_ISDIR
#undef S_ISFIFO
#undef S_ISCHR
#undef S_ISREG
#undef S_ISLNK
#undef S_ISBLK
#undef S_ISSOCK

#define S_ISDIR(m)      (((m) & _S_IFMT) == _S_IFDIR)
#define S_ISCHR(m)      (((m) & _S_IFMT) == _S_IFCHR)
#define S_ISFIFO(m)     (((m) & _S_IFMT) == _S_IFIFO)
#define S_ISREG(m)      (((m) & _S_IFMT) == _S_IFREG)
#define S_ISBLK(m)      (((m) & _S_IFMT) == _S_IFBLK)
#define S_ISLNK(m)      (((m) & _S_IFMT) == _S_IFLNK)
#define S_ISSOCK(m)     (((m) & _S_IFMT) == _S_IFSOCK)

#define O_NONBLOCK 0


// There were 369 years and 89 leap days from the Windows epoch
// (1601) to the Unix epoch (1970).
#define FILE_TIME_OFFSET_SECS (uint64_t(369 * 365 + 89) * (24 * 60 * 60))

TimeSpec filetime_to_timespec(LARGE_INTEGER li) {
  TimeSpec ret;
  ret.tv_sec = li.QuadPart / 10000000 - FILE_TIME_OFFSET_SECS;
  ret.tv_nsec = (li.QuadPart % 10000000) * 100;
  return ret;
}

TimeSpec filetime_to_timespec(FILETIME ft) {
  LARGE_INTEGER li;
  li.LowPart = ft.dwLowDateTime;
  li.HighPart = ft.dwHighDateTime;
  return filetime_to_timespec(li);
}

FILETIME timespec_to_filetime(TimeSpec ts) {
  LARGE_INTEGER li;
  li.QuadPart =
      ts.tv_nsec / 100 + (ts.tv_sec + FILE_TIME_OFFSET_SECS) * 10000000;
  FILETIME ft;
  ft.dwLowDateTime = li.LowPart;
  ft.dwHighDateTime = li.HighPart;
  return ft;
}

int set_errno(int e = GetLastError()) {
  errno = static_cast<int>(__win_err_to_errc(e));
  return -1;
}

class WinHandle {
public:
  WinHandle(const wchar_t *p, DWORD access, DWORD flags) {
    h = CreateFileW(
        p, access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | flags, nullptr);
  }
  ~WinHandle() {
    if (h != INVALID_HANDLE_VALUE)
      CloseHandle(h);
  }
  operator HANDLE() const { return h; }
  operator bool() const { return h != INVALID_HANDLE_VALUE; }

private:
  HANDLE h;
};

int stat_handle(HANDLE h, StatT *buf) {
  FILE_BASIC_INFO basic;
  if (!GetFileInformationByHandleEx(h, FileBasicInfo, &basic, sizeof(basic)))
    return set_errno();
  memset(buf, 0, sizeof(*buf));
  buf->st_mtim = filetime_to_timespec(basic.LastWriteTime);
  buf->st_atim = filetime_to_timespec(basic.LastAccessTime);
  buf->st_mode = 0555; // Read-only
  if (!(basic.FileAttributes & FILE_ATTRIBUTE_READONLY))
    buf->st_mode |= 0222; // Write
  if (basic.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
    buf->st_mode |= _S_IFDIR;
  } else {
    buf->st_mode |= _S_IFREG;
  }
  if (basic.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
    FILE_ATTRIBUTE_TAG_INFO tag;
    if (!GetFileInformationByHandleEx(h, FileAttributeTagInfo, &tag,
                                      sizeof(tag)))
      return set_errno();
    if (tag.ReparseTag == IO_REPARSE_TAG_SYMLINK)
      buf->st_mode = (buf->st_mode & ~_S_IFMT) | _S_IFLNK;
  }
  FILE_STANDARD_INFO standard;
  if (!GetFileInformationByHandleEx(h, FileStandardInfo, &standard,
                                    sizeof(standard)))
    return set_errno();
  buf->st_nlink = standard.NumberOfLinks;
  buf->st_size = standard.EndOfFile.QuadPart;
  BY_HANDLE_FILE_INFORMATION info;
  if (!GetFileInformationByHandle(h, &info))
    return set_errno();
  buf->st_dev = info.dwVolumeSerialNumber;
  memcpy(&buf->st_ino.id[0], &info.nFileIndexHigh, 4);
  memcpy(&buf->st_ino.id[4], &info.nFileIndexLow, 4);
  return 0;
}

int stat_file(const wchar_t *path, StatT *buf, DWORD flags) {
  WinHandle h(path, FILE_READ_ATTRIBUTES, flags);
  if (!h)
    return set_errno();
  int ret = stat_handle(h, buf);
  return ret;
}

int stat(const wchar_t *path, StatT *buf) { return stat_file(path, buf, 0); }

int lstat(const wchar_t *path, StatT *buf) {
  return stat_file(path, buf, FILE_FLAG_OPEN_REPARSE_POINT);
}

int fstat(int fd, StatT *buf) {
  HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
  return stat_handle(h, buf);
}

int mkdir(const wchar_t *path, int permissions) {
  (void)permissions;
  return _wmkdir(path);
}

int symlink_file_dir(const wchar_t *oldname, const wchar_t *newname,
                     bool is_dir) {
  path dest(oldname);
  dest.make_preferred();
  oldname = dest.c_str();
  DWORD flags = is_dir ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0;
  if (CreateSymbolicLinkW(newname, oldname,
                          flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE))
    return 0;
  int e = GetLastError();
  if (e != ERROR_INVALID_PARAMETER)
    return set_errno(e);
  if (CreateSymbolicLinkW(newname, oldname, flags))
    return 0;
  return set_errno();
}

int symlink_file(const wchar_t *oldname, const wchar_t *newname) {
  return symlink_file_dir(oldname, newname, false);
}

int symlink_dir(const wchar_t *oldname, const wchar_t *newname) {
  return symlink_file_dir(oldname, newname, true);
}

int link(const wchar_t *oldname, const wchar_t *newname) {
  if (CreateHardLinkW(newname, oldname, nullptr))
    return 0;
  return set_errno();
}

int remove(const wchar_t *path) {
  detail::WinHandle h(path, DELETE, FILE_FLAG_OPEN_REPARSE_POINT);
  if (!h)
    return set_errno();
  FILE_DISPOSITION_INFO info;
  info.DeleteFile = TRUE;
  if (!SetFileInformationByHandle(h, FileDispositionInfo, &info, sizeof(info)))
    return set_errno();
  return 0;
}

int truncate_handle(HANDLE h, off_t length) {
  LARGE_INTEGER size_param;
  size_param.QuadPart = length;
  if (!SetFilePointerEx(h, size_param, 0, FILE_BEGIN))
    return set_errno();
  if (!SetEndOfFile(h))
    return set_errno();
  return 0;
}

int ftruncate(int fd, off_t length) {
  HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
  return truncate_handle(h, length);
}

int truncate(const wchar_t *path, off_t length) {
  detail::WinHandle h(path, GENERIC_WRITE, 0);
  if (!h)
    return set_errno();
  return truncate_handle(h, length);
}

int rename(const wchar_t *from, const wchar_t *to) {
  if (!(MoveFileExW(from, to,
                    MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING |
                        MOVEFILE_WRITE_THROUGH)))
    return set_errno();
  return 0;
}

template <class... Args> int open(const wchar_t *filename, Args... args) {
  return _wopen(filename, args...);
}
int close(int fd) { return _close(fd); }
int chdir(const wchar_t *path) { return _wchdir(path); }

struct StatVFS {
  uint64_t f_frsize;
  uint64_t f_blocks;
  uint64_t f_bfree;
  uint64_t f_bavail;
};

int statvfs(const wchar_t *p, StatVFS *buf) {
  path dir = p;
  while (true) {
    error_code local_ec;
    const file_status st = status(dir, local_ec);
    if (!exists(st) || is_directory(st))
      break;
    path parent = dir.parent_path();
    if (parent == dir) {
      errno = ENOENT;
      return -1;
    }
    dir = parent;
  }
  ULARGE_INTEGER free_bytes_available_to_caller, total_number_of_bytes,
      total_number_of_free_bytes;
  if (!GetDiskFreeSpaceExW(dir.c_str(), &free_bytes_available_to_caller,
                           &total_number_of_bytes, &total_number_of_free_bytes))
    return set_errno();
  buf->f_frsize = 1;
  buf->f_blocks = total_number_of_bytes.QuadPart;
  buf->f_bfree = total_number_of_free_bytes.QuadPart;
  buf->f_bavail = free_bytes_available_to_caller.QuadPart;
  return 0;
}

wchar_t *getcwd(wchar_t *buff, size_t size) { return _wgetcwd(buff, size); }

wchar_t *realpath(const wchar_t *path, wchar_t *resolved_name) {
  // Only expected to be used with us allocating the buffer.
  _LIBCPP_ASSERT(resolved_name == nullptr,
                 "Windows realpath() assumes a null resolved_name");

  WinHandle h(path, FILE_READ_ATTRIBUTES, 0);
  if (!h) {
    set_errno();
    return nullptr;
  }
  size_t buff_size = MAX_PATH + 10;
  std::unique_ptr<wchar_t, decltype(&::free)> buff(
      static_cast<wchar_t *>(malloc(buff_size * sizeof(wchar_t))), &::free);
  DWORD retval = GetFinalPathNameByHandleW(
      h, buff.get(), buff_size, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
  if (retval > buff_size) {
    buff_size = retval;
    buff.reset(static_cast<wchar_t *>(malloc(buff_size * sizeof(wchar_t))));
    retval = GetFinalPathNameByHandleW(h, buff.get(), buff_size,
                                       FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
  }
  if (!retval) {
    set_errno();
    return nullptr;
  }
  wchar_t *ptr = buff.get();
  if (!wcsncmp(ptr, L"\\\\?\\", 4)) {
    if (ptr[5] == ':') { // \\?\X: -> X:
      memmove(&ptr[0], &ptr[4], (wcslen(&ptr[4]) + 1) * sizeof(wchar_t));
    } else if (!wcsncmp(&ptr[4], L"UNC\\", 4)) { // \\?\UNC\server -> \\server
      wcscpy(&ptr[0], L"\\\\");
      memmove(&ptr[2], &ptr[8], (wcslen(&ptr[8]) + 1) * sizeof(wchar_t));
    }
  }
  return buff.release();
}

#define AT_FDCWD -1
#define AT_SYMLINK_NOFOLLOW 1
using ModeT = int;

int fchmod_handle(HANDLE h, int perms) {
  FILE_BASIC_INFO basic;
  if (!GetFileInformationByHandleEx(h, FileBasicInfo, &basic, sizeof(basic)))
    return set_errno();
  DWORD orig_attributes = basic.FileAttributes;
  basic.FileAttributes &= ~FILE_ATTRIBUTE_READONLY;
  if ((perms & 0222) == 0)
    basic.FileAttributes |= FILE_ATTRIBUTE_READONLY;
  if (basic.FileAttributes != orig_attributes &&
      !SetFileInformationByHandle(h, FileBasicInfo, &basic, sizeof(basic)))
    return set_errno();
  return 0;
}

int fchmodat(int fd, const wchar_t *path, int perms, int flag) {
  DWORD attributes = GetFileAttributesW(path);
  if (attributes == INVALID_FILE_ATTRIBUTES)
    return set_errno();
  if (attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
      !(flag & AT_SYMLINK_NOFOLLOW)) {
    // If the file is a symlink, and we are supposed to operate on the target
    // of the symlink, we need to open a handle to it, without the
    // FILE_FLAG_OPEN_REPARSE_POINT flag, to open the destination of the
    // symlink, and operate on it via the handle.
    detail::WinHandle h(path, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, 0);
    if (!h)
      return set_errno();
    return fchmod_handle(h, perms);
  } else {
    // For a non-symlink, or if operating on the symlink itself instead of
    // its target, we can use SetFileAttributesW, saving a few calls.
    DWORD orig_attributes = attributes;
    attributes &= ~FILE_ATTRIBUTE_READONLY;
    if ((perms & 0222) == 0)
      attributes |= FILE_ATTRIBUTE_READONLY;
    if (attributes != orig_attributes && !SetFileAttributesW(path, attributes))
      return set_errno();
  }
  return 0;
}

int fchmod(int fd, int perms) {
  HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
  return fchmod_handle(h, perms);
}

#define MAX_SYMLINK_SIZE MAXIMUM_REPARSE_DATA_BUFFER_SIZE
using SSizeT = ::int64_t;

SSizeT readlink(const wchar_t *path, wchar_t *ret_buf, size_t bufsize) {
  uint8_t buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
  detail::WinHandle h(path, FILE_READ_ATTRIBUTES, FILE_FLAG_OPEN_REPARSE_POINT);
  if (!h)
    return set_errno();
  DWORD out;
  if (!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, nullptr, 0, buf, sizeof(buf),
                       &out, 0))
    return set_errno();
  const auto *reparse = reinterpret_cast<LIBCPP_REPARSE_DATA_BUFFER *>(buf);
  size_t path_buf_offset = offsetof(LIBCPP_REPARSE_DATA_BUFFER,
                                    SymbolicLinkReparseBuffer.PathBuffer[0]);
  if (out < path_buf_offset) {
    errno = EINVAL;
    return -1;
  }
  if (reparse->ReparseTag != IO_REPARSE_TAG_SYMLINK) {
    errno = EINVAL;
    return -1;
  }
  const auto &symlink = reparse->SymbolicLinkReparseBuffer;
  unsigned short name_offset, name_length;
  if (symlink.PrintNameLength == 0) {
    name_offset = symlink.SubstituteNameOffset;
    name_length = symlink.SubstituteNameLength;
  } else {
    name_offset = symlink.PrintNameOffset;
    name_length = symlink.PrintNameLength;
  }
  // name_offset/length are expressed in bytes, not in wchar_t
  if (path_buf_offset + name_offset + name_length > out) {
    errno = EINVAL;
    return -1;
  }
  if (name_length / sizeof(wchar_t) > bufsize) {
    errno = ENOMEM;
    return -1;
  }
  memcpy(ret_buf, &symlink.PathBuffer[name_offset / sizeof(wchar_t)],
         name_length);
  return name_length / sizeof(wchar_t);
}

#else
int symlink_file(const char *oldname, const char *newname) {
  return ::symlink(oldname, newname);
}
int symlink_dir(const char *oldname, const char *newname) {
  return ::symlink(oldname, newname);
}
using ::chdir;
using ::close;
using ::fchmod;
#if defined(AT_SYMLINK_NOFOLLOW) && defined(AT_FDCWD)
using ::fchmodat;
#endif
using ::fstat;
using ::ftruncate;
using ::getcwd;
using ::link;
using ::lstat;
using ::mkdir;
using ::open;
using ::readlink;
using ::realpath;
using ::remove;
using ::rename;
using ::stat;
using ::statvfs;
using ::truncate;

#define O_BINARY 0

using StatVFS = struct statvfs;
using ModeT = ::mode_t;
using SSizeT = ::ssize_t;

#endif

} // namespace
} // end namespace detail

_LIBCPP_END_NAMESPACE_FILESYSTEM

#endif // POSIX_COMPAT_H