1 // Copyright 2021 Jean Pierre Cimalando
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 // SPDX-License-Identifier: Apache-2.0
18 #include "ysfx_utils.hpp"
19 #include "base64/Base64.hpp"
20 #include <system_error>
27 # include <sys/stat.h>
28 # include <sys/types.h>
40 static_assert(sizeof(off_t
) == 8, "64-bit large file support is not enabled");
43 FILE *fopen_utf8(const char *path
, const char *mode
)
46 return _wfopen(widen(path
).c_str(), widen(mode
).c_str());
48 return fopen(path
, mode
);
52 int64_t fseek_lfs(FILE *stream
, int64_t off
, int whence
)
55 return _fseeki64(stream
, off
, whence
);
57 return fseeko(stream
, off
, whence
);
61 int64_t ftell_lfs(FILE *stream
)
64 return _ftelli64(stream
);
66 return ftello(stream
);
70 //------------------------------------------------------------------------------
74 struct scoped_c_locale
76 scoped_c_locale(int lc
, const char *name
);
80 scoped_c_locale(const scoped_c_locale
&) = delete;
81 scoped_c_locale
&operator=(const scoped_c_locale
&) = delete;
84 scoped_c_locale::scoped_c_locale(int lc
, const char *name
)
87 m_loc
= _create_locale(lc
, name
);
91 m_loc
= newlocale(LC_ALL_MASK
, name
, c_locale_t
{});
94 m_loc
= newlocale(LC_CTYPE_MASK
, name
, c_locale_t
{});
97 m_loc
= newlocale(LC_COLLATE_MASK
, name
, c_locale_t
{});
100 m_loc
= newlocale(LC_MONETARY_MASK
, name
, c_locale_t
{});
103 m_loc
= newlocale(LC_NUMERIC_MASK
, name
, c_locale_t
{});
106 m_loc
= newlocale(LC_TIME_MASK
, name
, c_locale_t
{});
109 m_loc
= newlocale(LC_MESSAGES_MASK
, name
, c_locale_t
{});
117 throw std::system_error(errno
, std::generic_category());
120 scoped_c_locale::~scoped_c_locale()
131 struct scoped_posix_uselocale
{
132 explicit scoped_posix_uselocale(c_locale_t loc
);
133 ~scoped_posix_uselocale();
138 scoped_posix_uselocale(const scoped_posix_uselocale
&) = delete;
139 scoped_posix_uselocale
&operator=(const scoped_posix_uselocale
&) = delete;
142 scoped_posix_uselocale::scoped_posix_uselocale(c_locale_t loc
)
147 m_old
= uselocale(loc
);
151 scoped_posix_uselocale::~scoped_posix_uselocale()
160 //------------------------------------------------------------------------------
162 c_locale_t
c_numeric_locale()
164 static scoped_c_locale
loc(LC_NUMERIC
, "C");
168 //------------------------------------------------------------------------------
170 double c_atof(const char *text
, c_locale_t loc
)
173 return _atof_l(text
, loc
);
175 scoped_posix_uselocale
use(loc
);
180 double c_strtod(const char *text
, char **endp
, c_locale_t loc
)
183 return _strtod_l(text
, endp
, loc
);
185 scoped_posix_uselocale
use(loc
);
186 return strtod(text
, endp
);
190 double dot_atof(const char *text
)
192 return c_atof(text
, c_numeric_locale());
195 double dot_strtod(const char *text
, char **endp
)
197 return c_strtod(text
, endp
, c_numeric_locale());
200 bool ascii_isspace(char c
)
203 case ' ': case '\f': case '\n': case '\r': case '\t': case '\v':
210 bool ascii_isalpha(char c
)
212 return (c
>= 'a' && c
<= 'z') || (c
>= 'A' && c
<= 'Z');
215 char ascii_tolower(char c
)
217 return (c
>= 'A' && c
<= 'Z') ? (c
- 'A' + 'a') : c
;
220 char ascii_toupper(char c
)
222 return (c
>= 'a' && c
<= 'z') ? (c
- 'a' + 'A') : c
;
225 int ascii_casecmp(const char *a
, const char *b
)
227 for (char ca
, cb
; (ca
= *a
++) | (cb
= *b
++); ) {
228 ca
= ascii_tolower(ca
);
229 cb
= ascii_tolower(cb
);
230 if (ca
< cb
) return -1;
231 if (ca
> cb
) return +1;
236 uint32_t latin1_toupper(uint32_t c
)
238 if (c
>= 'a' && c
<= 'z')
239 return c
- 'a' + 'A';
240 if ((c
>= 0xe0 && c
<= 0xf6) || (c
>= 0xf8 && c
<= 0xfe))
245 uint32_t latin1_tolower(uint32_t c
)
247 if (c
>= 'A' && c
<= 'Z')
248 return c
- 'A' + 'a';
249 if ((c
>= 0xc0 && c
<= 0xd6) || (c
>= 0xd8 && c
<= 0xde))
254 char *strdup_using_new(const char *src
)
256 size_t len
= strlen(src
);
257 char *dst
= new char[len
+ 1];
258 memcpy(dst
, src
, len
+ 1);
262 string_list
split_strings_noempty(const char *input
, bool(*pred
)(char))
270 for (char c
; (c
= *input
++) != '\0'; ) {
288 std::string
trim(const char *input
, bool(*pred
)(char))
290 const char *start
= input
;
291 while (*start
!= '\0' && pred(*start
))
294 const char *end
= start
+ strlen(start
);
295 while (end
> start
&& pred(*(end
- 1)))
298 return std::string(start
, end
);
301 //------------------------------------------------------------------------------
303 void pack_u32le(uint32_t value
, uint8_t data
[4])
305 data
[0] = value
& 0xff;
306 data
[1] = (value
>> 8) & 0xff;
307 data
[2] = (value
>> 16) & 0xff;
308 data
[3] = value
>> 24;
311 void pack_f32le(float value
, uint8_t data
[4])
314 memcpy(&u
, &value
, 4);
318 uint32_t unpack_u32le(const uint8_t data
[4])
320 return data
[0] | (data
[1] << 8) | (data
[2] << 16) | (data
[3] << 24);
323 float unpack_f32le(const uint8_t data
[4])
326 uint32_t u
= unpack_u32le(data
);
327 memcpy(&value
, &u
, 4);
331 //------------------------------------------------------------------------------
333 std::vector
<uint8_t> decode_base64(const char *text
, size_t len
)
335 return d_getChunkFromBase64String(text
, len
);
338 std::string
encode_base64(const uint8_t *data
, size_t len
)
340 return d_getBase64StringFromChunk(data
, len
);
343 //------------------------------------------------------------------------------
345 bool get_file_uid(const char *path
, file_uid
&uid
)
348 HANDLE handle
= CreateFileW(widen(path
).c_str(), GENERIC_READ
, FILE_SHARE_READ
, nullptr, OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
, nullptr);
349 if (handle
== INVALID_HANDLE_VALUE
)
351 bool success
= get_handle_file_uid((void *)handle
, uid
);
355 int fd
= open(path
, O_RDONLY
);
358 bool success
= get_descriptor_file_uid(fd
, uid
);
364 bool get_stream_file_uid(FILE *stream
, file_uid
&uid
)
367 int fd
= fileno(stream
);
371 int fd
= _fileno(stream
);
375 return get_descriptor_file_uid(fd
, uid
);
378 bool get_descriptor_file_uid(int fd
, file_uid
&uid
)
382 if (fstat(fd
, &st
) != 0)
384 uid
.first
= (uint64_t)st
.st_dev
;
385 uid
.second
= (uint64_t)st
.st_ino
;
388 HANDLE handle
= (HANDLE
)_get_osfhandle(fd
);
389 if (handle
== INVALID_HANDLE_VALUE
)
391 return get_handle_file_uid((void *)handle
, uid
);
396 bool get_handle_file_uid(void *handle
, file_uid
&uid
)
398 BY_HANDLE_FILE_INFORMATION info
;
399 if (!GetFileInformationByHandle((HANDLE
)handle
, &info
))
401 uid
.first
= info
.dwVolumeSerialNumber
;
402 uid
.second
= (uint64_t)info
.nFileIndexLow
| ((uint64_t)info
.nFileIndexHigh
<< 32);
407 //------------------------------------------------------------------------------
409 bool is_path_separator(char ch
)
414 return ch
== '/' || ch
== '\\';
418 split_path_t
split_path(const char *path
)
422 size_t npos
= ~(size_t)0;
424 for (size_t i
= 0; path
[i
] != '\0'; ++i
) {
425 if (is_path_separator(path
[i
]))
429 sp
.file
.assign(path
);
431 sp
.dir
.assign(path
, pos
+ 1);
432 sp
.file
.assign(path
+ pos
+ 1);
435 std::wstring wpath
= widen(path
);
436 std::unique_ptr
<wchar_t[]> drive
{new wchar_t[wpath
.size() + 1]{}};
437 std::unique_ptr
<wchar_t[]> dir
{new wchar_t[wpath
.size() + 1]{}};
438 std::unique_ptr
<wchar_t[]> fname
{new wchar_t[wpath
.size() + 1]{}};
439 std::unique_ptr
<wchar_t[]> ext
{new wchar_t[wpath
.size() + 1]{}};
440 _wsplitpath(wpath
.c_str(), drive
.get(), dir
.get(), fname
.get(), ext
.get());
441 sp
.drive
= narrow(drive
.get());
442 if (!drive
[0] || dir
[0] == L
'/' || dir
[0] == L
'\\')
443 sp
.dir
= narrow(dir
.get());
445 sp
.dir
= narrow(L
'\\' + std::wstring(dir
.get()));
446 sp
.file
= narrow(std::wstring(fname
.get()) + std::wstring(ext
.get()));
451 std::string
path_file_name(const char *path
)
453 return split_path(path
).file
;
456 std::string
path_directory(const char *path
)
458 split_path_t sp
= split_path(path
);
459 return sp
.dir
.empty() ? std::string("./") : (sp
.drive
+ sp
.dir
);
462 std::string
path_ensure_final_separator(const char *path
)
464 std::string
result(path
);
466 if (!result
.empty() && !is_path_separator(result
.back()))
467 result
.push_back('/');
472 bool path_has_suffix(const char *path
, const char *suffix
)
477 size_t plen
= strlen(path
);
478 size_t slen
= strlen(suffix
);
482 return path
[plen
- slen
- 1] == '.' &&
483 ascii_casecmp(suffix
, &path
[plen
- slen
]) == 0;
486 bool path_is_relative(const char *path
)
489 return !is_path_separator(path
[0]);
491 return !is_path_separator(split_path(path
).dir
.c_str()[0]);
495 //------------------------------------------------------------------------------
498 bool exists(const char *path
)
500 return access(path
, F_OK
) == 0;
503 string_list
list_directory(const char *path
)
507 DIR *dir
= opendir(path
);
510 auto dir_cleanup
= defer([dir
]() { closedir(dir
); });
515 pathbuf
.reserve(1024);
517 while (dirent
*ent
= readdir(dir
)) {
518 const char *name
= ent
->d_name
;
519 if (!strcmp(name
, ".") || !strcmp(name
, ".."))
522 pathbuf
.assign(name
);
523 if (ent
->d_type
== DT_DIR
)
524 pathbuf
.push_back('/');
526 list
.push_back(pathbuf
);
529 std::sort(list
.begin(), list
.end());
533 // void visit_directories(const char *rootpath, bool (*visit)(const std::string &, void *), void *data);
534 // NOTE: implemented in separate file `ysfx_utils_fts.cpp`
536 bool exists(const char *path
)
538 return _waccess(widen(path
).c_str(), 0) == 0;
541 string_list
list_directory(const char *path
)
545 std::wstring pattern
= widen(path
) + L
"\\*";
548 HANDLE handle
= FindFirstFileW(pattern
.c_str(), &fd
);
549 if (handle
== INVALID_HANDLE_VALUE
)
551 auto handle_cleanup
= defer([handle
]() { FindClose(handle
); });
556 const wchar_t *name
= fd
.cFileName
;
557 if (!wcscmp(name
, L
".") || !wcscmp(name
, L
".."))
560 std::string entry
= narrow(name
);
561 if ((fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) && !(fd
.dwFileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
))
562 entry
.push_back('/');
564 list
.push_back(std::move(entry
));
565 } while (FindNextFileW(handle
, &fd
));
567 std::sort(list
.begin(), list
.end());
571 void visit_directories(const char *rootpath
, bool (*visit
)(const std::string
&, void *), void *data
)
573 std::deque
<std::wstring
> dirs
;
574 dirs
.push_back(widen(path_ensure_final_separator(rootpath
)));
576 std::wstring pathbuf
;
577 pathbuf
.reserve(1024);
579 std::vector
<std::wstring
> entries
;
580 entries
.reserve(256);
582 while (!dirs
.empty()) {
583 std::wstring dir
= std::move(dirs
.front());
586 if (!visit(narrow(dir
), data
))
590 pathbuf
.append(L
"\\*");
593 HANDLE handle
= FindFirstFileW(pathbuf
.c_str(), &fd
);
594 if (handle
== INVALID_HANDLE_VALUE
)
596 auto handle_cleanup
= defer([handle
]() { FindClose(handle
); });
600 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
)
602 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
603 const wchar_t *name
= fd
.cFileName
;
604 if (!wcscmp(name
, L
".") || !wcscmp(name
, L
".."))
607 pathbuf
.append(name
);
608 pathbuf
.push_back(L
'\\');
609 entries
.push_back(pathbuf
);
611 } while (FindNextFileW(handle
, &fd
));
613 std::sort(entries
.begin(), entries
.end());
614 for (size_t n
= entries
.size(); n
-- > 0; )
615 dirs
.push_front(std::move(entries
[n
]));
620 int case_resolve(const char *root_
, const char *fragment
, std::string
&result
)
622 if (fragment
[0] == '\0')
625 std::string root
= path_ensure_final_separator(root_
);
628 pathbuf
.reserve(1024);
630 pathbuf
.assign(root
);
631 pathbuf
.append(fragment
);
632 if (exists(pathbuf
.c_str())) {
633 result
= std::move(pathbuf
);
639 string_list components
;
642 std::deque
<Item
> worklist
;
647 item
.components
= split_strings_noempty(fragment
, &is_path_separator
);
648 if (item
.components
.empty())
650 for (size_t i
= 0; i
+ 1 < item
.components
.size(); ++i
)
651 item
.components
[i
].push_back('/');
652 if (is_path_separator(fragment
[strlen(fragment
) - 1]))
653 item
.components
.back().push_back('/');
654 worklist
.push_back(std::move(item
));
657 while (!worklist
.empty()) {
658 Item item
= std::move(worklist
.front());
659 worklist
.pop_front();
661 for (const std::string
&entry
: list_directory(item
.root
.c_str())) {
662 if (ascii_casecmp(entry
.c_str(), item
.components
[0].c_str()) != 0)
665 if (item
.components
.size() == 1) {
666 pathbuf
.assign(item
.root
);
667 pathbuf
.append(entry
);
668 if (exists(pathbuf
.c_str())) {
669 result
= std::move(pathbuf
);
674 assert(item
.components
.size() > 1);
676 newitem
.root
= item
.root
+ entry
;
677 newitem
.components
.assign(item
.components
.begin() + 1, item
.components
.end());
678 worklist
.push_front(std::move(newitem
));
686 //------------------------------------------------------------------------------
689 std::wstring
widen(const std::string
&u8str
)
691 return widen(u8str
.data(), u8str
.size());
694 std::wstring
widen(const char *u8data
, size_t u8len
)
696 if (u8len
== ~(size_t)0)
697 u8len
= strlen(u8data
);
699 int wch
= MultiByteToWideChar(CP_UTF8
, 0, u8data
, (int)u8len
, nullptr, 0);
701 wstr
.resize((size_t)wch
);
702 MultiByteToWideChar(CP_UTF8
, 0, u8data
, (int)u8len
, &wstr
[0], wch
);
707 std::string
narrow(const std::wstring
&wstr
)
709 return narrow(wstr
.data(), wstr
.size());
712 std::string
narrow(const wchar_t *wdata
, size_t wlen
)
714 if (wlen
== ~(size_t)0)
715 wlen
= wcslen(wdata
);
717 int u8ch
= WideCharToMultiByte(CP_UTF8
, 0, wdata
, (int)wlen
, nullptr, 0, nullptr, nullptr);
719 u8str
.resize((size_t)u8ch
);
720 WideCharToMultiByte(CP_UTF8
, 0, wdata
, (int)wlen
, &u8str
[0], u8ch
, nullptr, nullptr);
728 //------------------------------------------------------------------------------
731 // our replacement `atof` for WDL, which is unaffected by current locale
732 extern "C" double ysfx_wdl_atof(const char *text
)
734 return ysfx::dot_atof(text
);