1 // Filesystem operation utilities -*- C++ -*-
3 // Copyright (C) 2014-2022 Free Software Foundation, Inc.
5 // This file is part of the GNU ISO C++ Library. This library is free
6 // software; you can redistribute it and/or modify it under the
7 // terms of the GNU General Public License as published by the
8 // Free Software Foundation; either version 3, or (at your option)
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // Under Section 7 of GPL version 3, you are granted additional
17 // permissions described in the GCC Runtime Library Exception, version
18 // 3.1, as published by the Free Software Foundation.
20 // You should have received a copy of the GNU General Public License and
21 // a copy of the GCC Runtime Library Exception along with this program;
22 // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
23 // <http://www.gnu.org/licenses/>.
25 #ifndef _GLIBCXX_OPS_COMMON_H
26 #define _GLIBCXX_OPS_COMMON_H 1
29 #include <bits/move.h> // std::__exchange
31 #ifdef _GLIBCXX_HAVE_UNISTD_H
33 # ifdef _GLIBCXX_HAVE_FCNTL_H
34 # include <fcntl.h> // AT_FDCWD, O_TRUNC etc.
36 # if defined(_GLIBCXX_HAVE_SYS_STAT_H) && defined(_GLIBCXX_HAVE_SYS_TYPES_H)
37 # include <sys/types.h>
38 # include <sys/stat.h>
41 #if !_GLIBCXX_USE_UTIMENSAT && _GLIBCXX_HAVE_UTIME_H
42 # include <utime.h> // utime
45 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
49 #ifdef NEED_DO_COPY_FILE
50 # include <filesystem>
51 # include <ext/stdio_filebuf.h>
52 # ifdef _GLIBCXX_USE_SENDFILE
53 # include <sys/sendfile.h> // sendfile
57 namespace std
_GLIBCXX_VISIBILITY(default)
59 _GLIBCXX_BEGIN_NAMESPACE_VERSION
61 // Get the last OS error (for POSIX this is just errno).
63 __last_system_error() noexcept
65 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
66 return {(int)::GetLastError(), std::system_category()};
68 return {errno
, std::generic_category()};
72 // Get an error code indicating unsupported functionality.
74 // This should be used when a function is unable to behave as specified
75 // due to an incomplete or partial implementation, e.g.
76 // filesystem::equivalent(a, b) if is_other(a) && is_other(b) is true.
78 // Use errc::function_not_supported for functions that are entirely
79 // unimplemented, e.g. create_symlink on Windows.
81 // Use errc::invalid_argument for requests to perform operations outside
82 // the spec, e.g. trying to copy a directory using filesystem::copy_file.
84 __unsupported() noexcept
87 return std::make_error_code(std::errc::not_supported
);
88 #elif defined EOPNOTSUPP
89 // This is supposed to be for socket operations
90 return std::make_error_code(std::errc::operation_not_supported
);
92 return std::make_error_code(std::errc::invalid_argument
);
100 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
101 // Adapt the Windows _wxxx functions to look like POSIX xxx, but for wchar_t*.
102 inline int open(const wchar_t* path
, int flags
)
103 { return ::_wopen(path
, flags
); }
105 inline int open(const wchar_t* path
, int flags
, int mode
)
106 { return ::_wopen(path
, flags
, mode
); }
108 inline int close(int fd
)
109 { return ::_close(fd
); }
111 typedef struct ::__stat64 stat_type
;
113 inline int stat(const wchar_t* path
, stat_type
* buffer
)
114 { return ::_wstat64(path
, buffer
); }
116 inline int lstat(const wchar_t* path
, stat_type
* buffer
)
118 // FIXME: symlinks not currently supported
119 return stat(path
, buffer
);
124 inline int chmod(const wchar_t* path
, mode_t mode
)
125 { return ::_wchmod(path
, mode
); }
127 inline int mkdir(const wchar_t* path
, mode_t
)
128 { return ::_wmkdir(path
); }
130 inline wchar_t* getcwd(wchar_t* buf
, size_t size
)
131 { return ::_wgetcwd(buf
, size
> (size_t)INT_MAX
? INT_MAX
: (int)size
); }
133 inline int chdir(const wchar_t* path
)
134 { return ::_wchdir(path
); }
136 #if !_GLIBCXX_USE_UTIMENSAT && _GLIBCXX_HAVE_UTIME_H
137 using utimbuf
= _utimbuf
;
139 inline int utime(const wchar_t* path
, utimbuf
* times
)
140 { return ::_wutime(path
, times
); }
143 inline int rename(const wchar_t* oldname
, const wchar_t* newname
)
145 if (MoveFileExW(oldname
, newname
,
146 MOVEFILE_REPLACE_EXISTING
| MOVEFILE_COPY_ALLOWED
))
148 if (GetLastError() == ERROR_ACCESS_DENIED
)
155 using off_t
= _off64_t
;
156 inline int truncate(const wchar_t* path
, _off64_t length
)
158 const int fd
= ::_wopen(path
, _O_BINARY
|_O_RDWR
);
161 const int ret
= ::ftruncate64(fd
, length
);
168 using char_type
= wchar_t;
169 #elif defined _GLIBCXX_HAVE_UNISTD_H
172 # ifdef _GLIBCXX_HAVE_SYS_STAT_H
173 typedef struct ::stat stat_type
;
175 # ifdef _GLIBCXX_USE_LSTAT
178 inline int lstat(const char* path
, stat_type
* buffer
)
179 { return stat(path
, buffer
); }
187 # if !_GLIBCXX_USE_UTIMENSAT && _GLIBCXX_USE_UTIME
193 # ifdef _GLIBCXX_HAVE_TRUNCATE
196 inline int truncate(const char* path
, off_t length
)
200 const int fd
= ::open(path
, O_WRONLY
|O_TRUNC
);
210 using char_type
= char;
211 #else // ! _GLIBCXX_FILESYSTEM_IS_WINDOWS && ! _GLIBCXX_HAVE_UNISTD_H
212 inline int open(const char*, int, ...) { errno
= ENOSYS
; return -1; }
213 inline int close(int) { errno
= ENOSYS
; return -1; }
215 inline int chmod(const char*, mode_t
) { errno
= ENOSYS
; return -1; }
216 inline int mkdir(const char*, mode_t
) { errno
= ENOSYS
; return -1; }
217 inline char* getcwd(char*, size_t) { errno
= ENOSYS
; return nullptr; }
218 inline int chdir(const char*) { errno
= ENOSYS
; return -1; }
219 inline int rename(const char*, const char*) { errno
= ENOSYS
; return -1; }
221 inline int truncate(const char*, off_t
) { errno
= ENOSYS
; return -1; }
222 using char_type
= char;
223 #endif // _GLIBCXX_FILESYSTEM_IS_WINDOWS
224 } // namespace __gnu_posix
226 template<typename Bitmask
>
227 inline bool is_set(Bitmask obj
, Bitmask bits
)
229 return (obj
& bits
) != Bitmask::none
;
233 is_not_found_errno(int err
) noexcept
235 return err
== ENOENT
|| err
== ENOTDIR
;
238 #ifdef _GLIBCXX_HAVE_SYS_STAT_H
239 using __gnu_posix::stat_type
;
241 inline std::chrono::system_clock::time_point
242 file_time(const stat_type
& st
, std::error_code
& ec
) noexcept
244 using namespace std::chrono
;
245 #ifdef _GLIBCXX_USE_ST_MTIM
246 time_t s
= st
.st_mtim
.tv_sec
;
247 nanoseconds ns
{st
.st_mtim
.tv_nsec
};
249 time_t s
= st
.st_mtime
;
254 // There are possible timespec values which will overflow
255 // chrono::system_clock::time_point but would not overflow
256 // __file_clock::time_point, due to its different epoch.
258 // By checking for overflow of the intermediate system_clock::duration
259 // type, we report an error for values which are actually representable
260 // in the file_time_type result type.
262 // Howard Hinnant's solution for this problem is to use
263 // duration<__int128>{s} + ns, which doesn't overflow.
264 // An alternative would be to do the epoch correction on s before
265 // the addition, and then go straight to file_time_type instead of
266 // going via chrono::system_clock::time_point.
268 // (This only applies to the C++17 Filesystem library, because for the
269 // Filesystem TS we don't have a distinct __file_clock, we just use the
270 // system clock for file timestamps).
271 if (seconds
{s
} >= floor
<seconds
>(system_clock::duration::max()))
273 ec
= std::make_error_code(std::errc::value_too_large
); // EOVERFLOW
274 return system_clock::time_point::min();
277 return system_clock::time_point
{seconds
{s
} + ns
};
280 struct copy_options_existing_file
282 bool skip
, update
, overwrite
;
285 #endif // _GLIBCXX_HAVE_SYS_STAT_H
287 } // namespace filesystem
289 // BEGIN/END macros must be defined before including this file.
290 _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
292 #ifdef _GLIBCXX_HAVE_SYS_STAT_H
293 using std::filesystem::__gnu_posix::stat_type
;
294 using std::filesystem::__gnu_posix::char_type
;
297 do_copy_file(const char_type
* from
, const char_type
* to
,
298 std::filesystem::copy_options_existing_file options
,
299 stat_type
* from_st
, stat_type
* to_st
,
300 std::error_code
& ec
) noexcept
;
303 do_space(const char_type
* pathname
,
304 uintmax_t& capacity
, uintmax_t& free
, uintmax_t& available
,
309 make_file_type(const stat_type
& st
) noexcept
311 #ifdef _GLIBCXX_HAVE_S_ISREG
312 if (S_ISREG(st
.st_mode
))
313 return file_type::regular
;
314 else if (S_ISDIR(st
.st_mode
))
315 return file_type::directory
;
316 else if (S_ISCHR(st
.st_mode
))
317 return file_type::character
;
318 else if (S_ISBLK(st
.st_mode
))
319 return file_type::block
;
320 else if (S_ISFIFO(st
.st_mode
))
321 return file_type::fifo
;
322 #ifdef S_ISLNK // not present in mingw
323 else if (S_ISLNK(st
.st_mode
))
324 return file_type::symlink
;
326 #ifdef S_ISSOCK // not present until POSIX:2001
327 else if (S_ISSOCK(st
.st_mode
))
328 return file_type::socket
;
331 return file_type::unknown
;
335 make_file_status(const stat_type
& st
) noexcept
339 static_cast<perms
>(st
.st_mode
) & perms::mask
343 inline std::filesystem::copy_options_existing_file
344 copy_file_options(copy_options opt
)
346 using std::filesystem::is_set
;
348 is_set(opt
, copy_options::skip_existing
),
349 is_set(opt
, copy_options::update_existing
),
350 is_set(opt
, copy_options::overwrite_existing
)
354 #ifdef NEED_DO_COPY_FILE
356 do_copy_file(const char_type
* from
, const char_type
* to
,
357 std::filesystem::copy_options_existing_file options
,
358 stat_type
* from_st
, stat_type
* to_st
,
359 std::error_code
& ec
) noexcept
361 namespace fs
= std::filesystem
;
362 namespace posix
= fs::__gnu_posix
;
367 if (to_st
== nullptr)
369 if (posix::stat(to
, &st1
))
371 const int err
= errno
;
372 if (!fs::is_not_found_errno(err
))
374 ec
.assign(err
, std::generic_category());
381 else if (to_st
== from_st
)
384 if (to_st
== nullptr)
385 t
= file_status
{file_type::not_found
};
387 t
= make_file_status(*to_st
);
389 if (from_st
== nullptr)
391 if (posix::stat(from
, &st2
))
393 ec
.assign(errno
, std::generic_category());
399 f
= make_file_status(*from_st
);
400 // _GLIBCXX_RESOLVE_LIB_DEFECTS
401 // 2712. copy_file() has a number of unspecified error conditions
402 if (!is_regular_file(f
))
404 ec
= std::make_error_code(std::errc::invalid_argument
);
410 if (!is_regular_file(t
))
412 ec
= std::make_error_code(std::errc::invalid_argument
);
416 if (to_st
->st_dev
== from_st
->st_dev
417 && to_st
->st_ino
== from_st
->st_ino
)
419 ec
= std::make_error_code(std::errc::file_exists
);
428 else if (options
.update
)
430 const auto from_mtime
= fs::file_time(*from_st
, ec
);
433 if ((from_mtime
<= fs::file_time(*to_st
, ec
)) || ec
)
436 else if (!options
.overwrite
)
438 ec
= std::make_error_code(std::errc::file_exists
);
441 else if (!is_regular_file(t
))
443 ec
= std::make_error_code(std::errc::invalid_argument
);
449 ~CloseFD() { if (fd
!= -1) posix::close(fd
); }
450 bool close() { return posix::close(std::__exchange(fd
, -1)) == 0; }
454 int iflag
= O_RDONLY
;
455 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
459 CloseFD in
= { posix::open(from
, iflag
) };
462 ec
.assign(errno
, std::generic_category());
465 int oflag
= O_WRONLY
|O_CREAT
;
466 if (options
.overwrite
|| options
.update
)
470 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
473 CloseFD out
= { posix::open(to
, oflag
, S_IWUSR
) };
476 if (errno
== EEXIST
&& options
.skip
)
479 ec
.assign(errno
, std::generic_category());
483 #if defined _GLIBCXX_USE_FCHMOD && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
484 if (::fchmod(out
.fd
, from_st
->st_mode
))
485 #elif defined _GLIBCXX_USE_FCHMODAT && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
486 if (::fchmodat(AT_FDCWD
, to
, from_st
->st_mode
, 0))
488 if (posix::chmod(to
, from_st
->st_mode
))
491 ec
.assign(errno
, std::generic_category());
495 size_t count
= from_st
->st_size
;
496 #if defined _GLIBCXX_USE_SENDFILE && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
498 ssize_t n
= ::sendfile(out
.fd
, in
.fd
, &offset
, count
);
499 if (n
< 0 && errno
!= ENOSYS
&& errno
!= EINVAL
)
501 ec
.assign(errno
, std::generic_category());
504 if ((size_t)n
== count
)
506 if (!out
.close() || !in
.close())
508 ec
.assign(errno
, std::generic_category());
516 #endif // _GLIBCXX_USE_SENDFILE
519 __gnu_cxx::stdio_filebuf
<char> sbin(in
.fd
, ios::in
|ios::binary
);
520 __gnu_cxx::stdio_filebuf
<char> sbout(out
.fd
, ios::out
|ios::binary
);
527 #ifdef _GLIBCXX_USE_SENDFILE
533 const auto p1
= sbin
.pubseekoff(n
, ios::beg
, ios::in
);
534 const auto p2
= sbout
.pubseekoff(n
, ios::beg
, ios::out
);
536 const std::streampos
errpos(std::streamoff(-1));
537 if (p1
== errpos
|| p2
== errpos
)
539 ec
= std::make_error_code(std::errc::io_error
);
545 if (count
&& !(std::ostream(&sbout
) << &sbin
))
547 ec
= std::make_error_code(std::errc::io_error
);
550 if (!sbout
.close() || !sbin
.close())
552 ec
.assign(errno
, std::generic_category());
558 #endif // NEED_DO_COPY_FILE
561 #pragma GCC diagnostic push
562 #pragma GCC diagnostic ignored "-Wunused-parameter"
564 do_space(const char_type
* pathname
,
565 uintmax_t& capacity
, uintmax_t& free
, uintmax_t& available
,
568 #ifdef _GLIBCXX_HAVE_SYS_STATVFS_H
570 if (::statvfs(pathname
, &f
))
571 ec
.assign(errno
, std::generic_category());
574 if (f
.f_frsize
!= (unsigned long)-1)
576 const uintmax_t fragment_size
= f
.f_frsize
;
577 const fsblkcnt_t unknown
= -1;
578 if (f
.f_blocks
!= unknown
)
579 capacity
= f
.f_blocks
* fragment_size
;
580 if (f
.f_bfree
!= unknown
)
581 free
= f
.f_bfree
* fragment_size
;
582 if (f
.f_bavail
!= unknown
)
583 available
= f
.f_bavail
* fragment_size
;
587 #elif _GLIBCXX_FILESYSTEM_IS_WINDOWS
588 ULARGE_INTEGER bytes_avail
= {}, bytes_total
= {}, bytes_free
= {};
589 if (GetDiskFreeSpaceExW(pathname
, &bytes_avail
, &bytes_total
, &bytes_free
))
591 if (bytes_total
.QuadPart
!= 0)
592 capacity
= bytes_total
.QuadPart
;
593 if (bytes_free
.QuadPart
!= 0)
594 free
= bytes_free
.QuadPart
;
595 if (bytes_avail
.QuadPart
!= 0)
596 available
= bytes_avail
.QuadPart
;
600 ec
= std::__last_system_error();
602 ec
= std::make_error_code(std::errc::function_not_supported
);
605 #pragma GCC diagnostic pop
606 #endif // NEED_DO_SPACE
608 #endif // _GLIBCXX_HAVE_SYS_STAT_H
610 // Find OS-specific name of temporary directory from the environment,
611 // Caller must check that the path is an accessible directory.
612 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
614 get_temp_directory_from_env(error_code
& ec
)
621 len
= GetTempPathW(buf
.size(), buf
.data());
622 } while (len
> buf
.size());
625 ec
= __last_system_error();
634 get_temp_directory_from_env(error_code
& ec
) noexcept
637 for (auto env
: { "TMPDIR", "TMP", "TEMP", "TEMPDIR" })
639 #if _GLIBCXX_HAVE_SECURE_GETENV
640 auto tmpdir
= ::secure_getenv(env
);
642 auto tmpdir
= ::getenv(env
);
651 _GLIBCXX_END_NAMESPACE_FILESYSTEM
653 _GLIBCXX_END_NAMESPACE_VERSION
656 #endif // _GLIBCXX_OPS_COMMON_H