1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include "system.h"
4
5#include "lock.h"
6#include "logger.h"
7#include "windows.h"
8
9#include <sys/types.h>
10
11#include <atomic>
12#include <cctype>
13#include <charconv>
14#include <chrono>
15#include <cinttypes>
16#include <cmath>
17#include <cstdarg>
18#include <cstdio>
19#include <cstring>
20#include <iomanip> // std::get_time
21#include <iterator> // std::size
22#include <mutex>
23#include <sstream> // std::istringstream
24#include <string_view>
25
26#if defined(CONF_WEBSOCKETS)
27#include <engine/shared/websockets.h>
28#endif
29
30#if defined(CONF_FAMILY_UNIX)
31#include <sys/stat.h>
32#include <sys/time.h>
33#include <sys/utsname.h>
34#include <sys/wait.h>
35#include <unistd.h>
36
37#include <csignal>
38#include <locale>
39
40/* unix net includes */
41#include <arpa/inet.h>
42#include <dirent.h>
43#include <netdb.h>
44#include <netinet/in.h>
45#include <pthread.h>
46#include <sys/ioctl.h>
47#include <sys/socket.h>
48
49#include <cerrno>
50
51#if defined(CONF_PLATFORM_MACOS)
52// some lock and pthread functions are already defined in headers
53// included from Carbon.h
54// this prevents having duplicate definitions of those
55#define _lock_set_user_
56#define _task_user_
57
58#include <Carbon/Carbon.h>
59#include <CoreFoundation/CoreFoundation.h>
60#include <mach-o/dyld.h>
61#include <mach/mach_time.h>
62
63#if defined(__MAC_10_10) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_10
64#include <pthread/qos.h>
65#endif
66#endif
67
68#elif defined(CONF_FAMILY_WINDOWS)
69#include <io.h>
70#include <objbase.h>
71#include <process.h>
72#include <shellapi.h>
73#include <shlobj.h> // SHGetKnownFolderPath
74#include <shlwapi.h>
75#include <wincrypt.h>
76#include <windows.h>
77#include <winsock2.h>
78#include <ws2tcpip.h>
79
80#include <cerrno>
81#include <cfenv>
82#else
83#error NOT IMPLEMENTED
84#endif
85
86#if defined(CONF_PLATFORM_SOLARIS)
87#include <sys/filio.h>
88#endif
89
90#if defined(CONF_PLATFORM_EMSCRIPTEN)
91#include <emscripten/emscripten.h>
92#endif
93
94static NETSTATS network_stats = {.sent_packets: 0};
95
96#define VLEN 128
97#define PACKETSIZE 1400
98typedef struct
99{
100#ifdef CONF_PLATFORM_LINUX
101 int pos;
102 int size;
103 struct mmsghdr msgs[VLEN];
104 struct iovec iovecs[VLEN];
105 char bufs[VLEN][PACKETSIZE];
106 char sockaddrs[VLEN][128];
107#else
108 char buf[PACKETSIZE];
109#endif
110} NETSOCKET_BUFFER;
111
112void net_buffer_init(NETSOCKET_BUFFER *buffer);
113void net_buffer_reinit(NETSOCKET_BUFFER *buffer);
114void net_buffer_simple(NETSOCKET_BUFFER *buffer, char **buf, int *size);
115
116struct NETSOCKET_INTERNAL
117{
118 int type;
119 int ipv4sock;
120 int ipv6sock;
121 int web_ipv4sock;
122 int web_ipv6sock;
123
124 NETSOCKET_BUFFER buffer;
125};
126static NETSOCKET_INTERNAL invalid_socket = {.type: NETTYPE_INVALID, .ipv4sock: -1, .ipv6sock: -1, .web_ipv4sock: -1, .web_ipv6sock: -1};
127
128std::atomic_bool dbg_assert_failing = false;
129DBG_ASSERT_HANDLER dbg_assert_handler;
130
131bool dbg_assert_has_failed()
132{
133 return dbg_assert_failing.load(m: std::memory_order_acquire);
134}
135
136void dbg_assert_imp(const char *filename, int line, const char *fmt, ...)
137{
138 const bool already_failing = dbg_assert_has_failed();
139 dbg_assert_failing.store(i: true, m: std::memory_order_release);
140 char msg[512];
141 va_list args;
142 va_start(args, fmt);
143 str_format_v(buffer: msg, buffer_size: sizeof(msg), format: fmt, args);
144 char error[1024];
145 str_format(buffer: error, buffer_size: sizeof(error), format: "%s(%d): %s", filename, line, msg);
146 va_end(args);
147 log_error("assert", "%s", error);
148 if(!already_failing)
149 {
150 DBG_ASSERT_HANDLER handler = dbg_assert_handler;
151 if(handler)
152 handler(error);
153 }
154 log_global_logger_finish();
155 dbg_break();
156}
157
158void dbg_break()
159{
160#ifdef __GNUC__
161 __builtin_trap();
162#else
163 abort();
164#endif
165}
166
167void dbg_assert_set_handler(DBG_ASSERT_HANDLER handler)
168{
169 dbg_assert_handler = std::move(handler);
170}
171
172void dbg_msg(const char *sys, const char *fmt, ...)
173{
174 va_list args;
175 va_start(args, fmt);
176 log_log_v(level: LEVEL_INFO, sys, fmt, args);
177 va_end(args);
178}
179
180/* */
181
182void mem_copy(void *dest, const void *source, size_t size)
183{
184 memcpy(dest: dest, src: source, n: size);
185}
186
187void mem_move(void *dest, const void *source, size_t size)
188{
189 memmove(dest: dest, src: source, n: size);
190}
191
192int mem_comp(const void *a, const void *b, size_t size)
193{
194 return memcmp(s1: a, s2: b, n: size);
195}
196
197bool mem_has_null(const void *block, size_t size)
198{
199 const unsigned char *bytes = (const unsigned char *)block;
200 for(size_t i = 0; i < size; i++)
201 {
202 if(bytes[i] == 0)
203 {
204 return true;
205 }
206 }
207 return false;
208}
209
210IOHANDLE io_open(const char *filename, int flags)
211{
212 dbg_assert(flags == IOFLAG_READ || flags == IOFLAG_WRITE || flags == IOFLAG_APPEND, "flags must be read, write or append");
213#if defined(CONF_FAMILY_WINDOWS)
214 const std::wstring wide_filename = windows_utf8_to_wide(filename);
215 DWORD desired_access;
216 DWORD creation_disposition;
217 const char *open_mode;
218 if((flags & IOFLAG_READ) != 0)
219 {
220 desired_access = FILE_READ_DATA;
221 creation_disposition = OPEN_EXISTING;
222 open_mode = "rb";
223 }
224 else if(flags == IOFLAG_WRITE)
225 {
226 desired_access = FILE_WRITE_DATA;
227 creation_disposition = CREATE_ALWAYS;
228 open_mode = "wb";
229 }
230 else if(flags == IOFLAG_APPEND)
231 {
232 desired_access = FILE_APPEND_DATA;
233 creation_disposition = OPEN_ALWAYS;
234 open_mode = "ab";
235 }
236 else
237 {
238 dbg_assert_failed("logic error");
239 }
240 HANDLE handle = CreateFileW(wide_filename.c_str(), desired_access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, creation_disposition, FILE_ATTRIBUTE_NORMAL, nullptr);
241 if(handle == INVALID_HANDLE_VALUE)
242 return nullptr;
243 const int file_descriptor = _open_osfhandle((intptr_t)handle, 0);
244 dbg_assert(file_descriptor != -1, "_open_osfhandle failure");
245 FILE *file_stream = _fdopen(file_descriptor, open_mode);
246 dbg_assert(file_stream != nullptr, "_fdopen failure");
247 return file_stream;
248#else
249 const char *open_mode;
250 if((flags & IOFLAG_READ) != 0)
251 {
252 open_mode = "rb";
253 }
254 else if(flags == IOFLAG_WRITE)
255 {
256 open_mode = "wb";
257 }
258 else if(flags == IOFLAG_APPEND)
259 {
260 open_mode = "ab";
261 }
262 else
263 {
264 dbg_assert_failed("Invalid flags: %d", flags);
265 }
266 return fopen(filename: filename, modes: open_mode);
267#endif
268}
269
270unsigned io_read(IOHANDLE io, void *buffer, unsigned size)
271{
272 return fread(ptr: buffer, size: 1, n: size, stream: (FILE *)io);
273}
274
275bool io_read_all(IOHANDLE io, void **result, unsigned *result_len)
276{
277 // Loading files larger than 1 GiB into memory is not supported.
278 constexpr int64_t MAX_FILE_SIZE = (int64_t)1024 * 1024 * 1024;
279
280 int64_t real_len = io_length(io);
281 if(real_len > MAX_FILE_SIZE)
282 {
283 *result = nullptr;
284 *result_len = 0;
285 return false;
286 }
287
288 int64_t len = real_len < 0 ? 1024 : real_len; // use default initial size if we couldn't get the length
289 char *buffer = (char *)malloc(size: len + 1);
290 int64_t read = io_read(io, buffer, size: len + 1); // +1 to check if the file size is larger than expected
291 if(read < len)
292 {
293 buffer = (char *)realloc(ptr: buffer, size: read + 1);
294 len = read;
295 }
296 else if(read > len)
297 {
298 int64_t cap = 2 * read;
299 if(cap > MAX_FILE_SIZE)
300 {
301 free(ptr: buffer);
302 *result = nullptr;
303 *result_len = 0;
304 return false;
305 }
306 len = read;
307 buffer = (char *)realloc(ptr: buffer, size: cap);
308 while((read = io_read(io, buffer: buffer + len, size: cap - len)) != 0)
309 {
310 len += read;
311 if(len == cap)
312 {
313 cap *= 2;
314 if(cap > MAX_FILE_SIZE)
315 {
316 free(ptr: buffer);
317 *result = nullptr;
318 *result_len = 0;
319 return false;
320 }
321 buffer = (char *)realloc(ptr: buffer, size: cap);
322 }
323 }
324 buffer = (char *)realloc(ptr: buffer, size: len + 1);
325 }
326 buffer[len] = 0;
327 *result = buffer;
328 *result_len = len;
329 return true;
330}
331
332char *io_read_all_str(IOHANDLE io)
333{
334 void *buffer;
335 unsigned len;
336 if(!io_read_all(io, result: &buffer, result_len: &len))
337 {
338 return nullptr;
339 }
340 if(mem_has_null(block: buffer, size: len))
341 {
342 free(ptr: buffer);
343 return nullptr;
344 }
345 return (char *)buffer;
346}
347
348int io_skip(IOHANDLE io, int64_t size)
349{
350 return io_seek(io, offset: size, origin: IOSEEK_CUR);
351}
352
353int io_seek(IOHANDLE io, int64_t offset, ESeekOrigin origin)
354{
355 int real_origin;
356 switch(origin)
357 {
358 case IOSEEK_START:
359 real_origin = SEEK_SET;
360 break;
361 case IOSEEK_CUR:
362 real_origin = SEEK_CUR;
363 break;
364 case IOSEEK_END:
365 real_origin = SEEK_END;
366 break;
367 default:
368 dbg_assert_failed("Invalid origin: %d", origin);
369 }
370#if defined(CONF_FAMILY_WINDOWS)
371 return _fseeki64((FILE *)io, offset, real_origin);
372#else
373 return fseeko(stream: (FILE *)io, off: offset, whence: real_origin);
374#endif
375}
376
377int64_t io_tell(IOHANDLE io)
378{
379#if defined(CONF_FAMILY_WINDOWS)
380 return _ftelli64((FILE *)io);
381#else
382 return ftello(stream: (FILE *)io);
383#endif
384}
385
386int64_t io_length(IOHANDLE io)
387{
388 if(io_seek(io, offset: 0, origin: IOSEEK_END) != 0)
389 {
390 return -1;
391 }
392 const int64_t length = io_tell(io);
393 if(io_seek(io, offset: 0, origin: IOSEEK_START) != 0)
394 {
395 return -1;
396 }
397 return length;
398}
399
400unsigned io_write(IOHANDLE io, const void *buffer, unsigned size)
401{
402 return fwrite(ptr: buffer, size: 1, n: size, s: (FILE *)io);
403}
404
405bool io_write_newline(IOHANDLE io)
406{
407#if defined(CONF_FAMILY_WINDOWS)
408 return io_write(io, "\r\n", 2) == 2;
409#else
410 return io_write(io, buffer: "\n", size: 1) == 1;
411#endif
412}
413
414int io_close(IOHANDLE io)
415{
416 return fclose(stream: (FILE *)io) != 0;
417}
418
419int io_flush(IOHANDLE io)
420{
421 return fflush(stream: (FILE *)io);
422}
423
424int io_sync(IOHANDLE io)
425{
426 if(io_flush(io))
427 {
428 return 1;
429 }
430#if defined(CONF_FAMILY_WINDOWS)
431 return FlushFileBuffers((HANDLE)_get_osfhandle(_fileno((FILE *)io))) == FALSE;
432#else
433 return fsync(fd: fileno(stream: (FILE *)io)) != 0;
434#endif
435}
436
437int io_error(IOHANDLE io)
438{
439 return ferror(stream: (FILE *)io);
440}
441
442IOHANDLE io_stdin()
443{
444 return stdin;
445}
446
447IOHANDLE io_stdout()
448{
449 return stdout;
450}
451
452IOHANDLE io_stderr()
453{
454 return stderr;
455}
456
457IOHANDLE io_current_exe()
458{
459 // From https://stackoverflow.com/a/1024937.
460#if defined(CONF_FAMILY_WINDOWS)
461 wchar_t wide_path[IO_MAX_PATH_LENGTH];
462 if(GetModuleFileNameW(nullptr, wide_path, std::size(wide_path)) == 0 || GetLastError() != ERROR_SUCCESS)
463 {
464 return nullptr;
465 }
466 const std::optional<std::string> path = windows_wide_to_utf8(wide_path);
467 return path.has_value() ? io_open(path.value().c_str(), IOFLAG_READ) : nullptr;
468#elif defined(CONF_PLATFORM_MACOS)
469 char path[IO_MAX_PATH_LENGTH];
470 uint32_t path_size = sizeof(path);
471 if(_NSGetExecutablePath(path, &path_size))
472 {
473 return 0;
474 }
475 return io_open(path, IOFLAG_READ);
476#else
477 static const char *NAMES[] = {
478 "/proc/self/exe", // Linux, Android
479 "/proc/curproc/exe", // NetBSD
480 "/proc/curproc/file", // DragonFly
481 };
482 for(auto &name : NAMES)
483 {
484 IOHANDLE result = io_open(filename: name, flags: IOFLAG_READ);
485 if(result)
486 {
487 return result;
488 }
489 }
490 return 0;
491#endif
492}
493
494#define ASYNC_BUFSIZE (8 * 1024)
495#define ASYNC_LOCAL_BUFSIZE (64 * 1024)
496
497struct ASYNCIO
498{
499 CLock lock;
500 IOHANDLE io;
501 SEMAPHORE sphore;
502 void *thread;
503
504 unsigned char *buffer;
505 unsigned int buffer_size;
506 unsigned int read_pos;
507 unsigned int write_pos;
508
509 int error;
510 unsigned char finish;
511 unsigned char refcount;
512};
513
514enum
515{
516 ASYNCIO_RUNNING,
517 ASYNCIO_CLOSE,
518 ASYNCIO_EXIT,
519};
520
521struct BUFFERS
522{
523 unsigned char *buf1;
524 unsigned int len1;
525 unsigned char *buf2;
526 unsigned int len2;
527};
528
529static void buffer_ptrs(ASYNCIO *aio, struct BUFFERS *buffers)
530{
531 mem_zero(block: buffers, size: sizeof(*buffers));
532 if(aio->read_pos < aio->write_pos)
533 {
534 buffers->buf1 = aio->buffer + aio->read_pos;
535 buffers->len1 = aio->write_pos - aio->read_pos;
536 }
537 else if(aio->read_pos > aio->write_pos)
538 {
539 buffers->buf1 = aio->buffer + aio->read_pos;
540 buffers->len1 = aio->buffer_size - aio->read_pos;
541 buffers->buf2 = aio->buffer;
542 buffers->len2 = aio->write_pos;
543 }
544}
545
546static void aio_handle_free_and_unlock(ASYNCIO *aio) RELEASE(aio->lock)
547{
548 int do_free;
549 aio->refcount--;
550
551 do_free = aio->refcount == 0;
552 aio->lock.unlock();
553 if(do_free)
554 {
555 free(ptr: aio->buffer);
556 sphore_destroy(sem: &aio->sphore);
557 delete aio;
558 }
559}
560
561static void aio_thread(void *user)
562{
563 ASYNCIO *aio = (ASYNCIO *)user;
564
565 aio->lock.lock();
566 while(true)
567 {
568 struct BUFFERS buffers;
569 int result_io_error;
570 unsigned char local_buffer[ASYNC_LOCAL_BUFSIZE];
571 unsigned int local_buffer_len = 0;
572
573 if(aio->read_pos == aio->write_pos)
574 {
575 if(aio->finish != ASYNCIO_RUNNING)
576 {
577 if(aio->finish == ASYNCIO_CLOSE)
578 {
579 io_close(io: aio->io);
580 }
581 aio_handle_free_and_unlock(aio);
582 break;
583 }
584 aio->lock.unlock();
585 sphore_wait(sem: &aio->sphore);
586 aio->lock.lock();
587 continue;
588 }
589
590 buffer_ptrs(aio, buffers: &buffers);
591 if(buffers.buf1)
592 {
593 if(buffers.len1 > sizeof(local_buffer) - local_buffer_len)
594 {
595 buffers.len1 = sizeof(local_buffer) - local_buffer_len;
596 }
597 mem_copy(dest: local_buffer + local_buffer_len, source: buffers.buf1, size: buffers.len1);
598 local_buffer_len += buffers.len1;
599 if(buffers.buf2)
600 {
601 if(buffers.len2 > sizeof(local_buffer) - local_buffer_len)
602 {
603 buffers.len2 = sizeof(local_buffer) - local_buffer_len;
604 }
605 mem_copy(dest: local_buffer + local_buffer_len, source: buffers.buf2, size: buffers.len2);
606 local_buffer_len += buffers.len2;
607 }
608 }
609 aio->read_pos = (aio->read_pos + buffers.len1 + buffers.len2) % aio->buffer_size;
610 aio->lock.unlock();
611
612 io_write(io: aio->io, buffer: local_buffer, size: local_buffer_len);
613 io_flush(io: aio->io);
614 result_io_error = io_error(io: aio->io);
615
616 aio->lock.lock();
617 aio->error = result_io_error;
618 }
619}
620
621ASYNCIO *aio_new(IOHANDLE io)
622{
623 ASYNCIO *aio = new ASYNCIO;
624 if(!aio)
625 {
626 return nullptr;
627 }
628 aio->io = io;
629 sphore_init(sem: &aio->sphore);
630 aio->thread = nullptr;
631
632 aio->buffer = (unsigned char *)malloc(ASYNC_BUFSIZE);
633 if(!aio->buffer)
634 {
635 sphore_destroy(sem: &aio->sphore);
636 delete aio;
637 return nullptr;
638 }
639 aio->buffer_size = ASYNC_BUFSIZE;
640 aio->read_pos = 0;
641 aio->write_pos = 0;
642 aio->error = 0;
643 aio->finish = ASYNCIO_RUNNING;
644 aio->refcount = 2;
645
646 aio->thread = thread_init(threadfunc: aio_thread, user: aio, name: "aio");
647 if(!aio->thread)
648 {
649 free(ptr: aio->buffer);
650 sphore_destroy(sem: &aio->sphore);
651 delete aio;
652 return nullptr;
653 }
654 return aio;
655}
656
657static unsigned int buffer_len(ASYNCIO *aio)
658{
659 if(aio->write_pos >= aio->read_pos)
660 {
661 return aio->write_pos - aio->read_pos;
662 }
663 else
664 {
665 return aio->buffer_size + aio->write_pos - aio->read_pos;
666 }
667}
668
669static unsigned int next_buffer_size(unsigned int cur_size, unsigned int need_size)
670{
671 while(cur_size < need_size)
672 {
673 cur_size *= 2;
674 }
675 return cur_size;
676}
677
678void aio_lock(ASYNCIO *aio) ACQUIRE(aio->lock)
679{
680 aio->lock.lock();
681}
682
683void aio_unlock(ASYNCIO *aio) RELEASE(aio->lock)
684{
685 aio->lock.unlock();
686 sphore_signal(sem: &aio->sphore);
687}
688
689void aio_write_unlocked(ASYNCIO *aio, const void *buffer, unsigned size)
690{
691 unsigned int remaining;
692 remaining = aio->buffer_size - buffer_len(aio);
693
694 // Don't allow full queue to distinguish between empty and full queue.
695 if(size < remaining)
696 {
697 unsigned int remaining_contiguous = aio->buffer_size - aio->write_pos;
698 if(size > remaining_contiguous)
699 {
700 mem_copy(dest: aio->buffer + aio->write_pos, source: buffer, size: remaining_contiguous);
701 size -= remaining_contiguous;
702 buffer = ((unsigned char *)buffer) + remaining_contiguous;
703 aio->write_pos = 0;
704 }
705 mem_copy(dest: aio->buffer + aio->write_pos, source: buffer, size);
706 aio->write_pos = (aio->write_pos + size) % aio->buffer_size;
707 }
708 else
709 {
710 // Add 1 so the new buffer isn't completely filled.
711 unsigned int new_written = buffer_len(aio) + size + 1;
712 unsigned int next_size = next_buffer_size(cur_size: aio->buffer_size, need_size: new_written);
713 unsigned int next_len = 0;
714 unsigned char *next_buffer = (unsigned char *)malloc(size: next_size);
715
716 struct BUFFERS buffers;
717 buffer_ptrs(aio, buffers: &buffers);
718 if(buffers.buf1)
719 {
720 mem_copy(dest: next_buffer + next_len, source: buffers.buf1, size: buffers.len1);
721 next_len += buffers.len1;
722 if(buffers.buf2)
723 {
724 mem_copy(dest: next_buffer + next_len, source: buffers.buf2, size: buffers.len2);
725 next_len += buffers.len2;
726 }
727 }
728 mem_copy(dest: next_buffer + next_len, source: buffer, size);
729 next_len += size;
730
731 free(ptr: aio->buffer);
732 aio->buffer = next_buffer;
733 aio->buffer_size = next_size;
734 aio->read_pos = 0;
735 aio->write_pos = next_len;
736 }
737}
738
739void aio_write(ASYNCIO *aio, const void *buffer, unsigned size)
740{
741 aio_lock(aio);
742 aio_write_unlocked(aio, buffer, size);
743 aio_unlock(aio);
744}
745
746void aio_write_newline_unlocked(ASYNCIO *aio)
747{
748#if defined(CONF_FAMILY_WINDOWS)
749 aio_write_unlocked(aio, "\r\n", 2);
750#else
751 aio_write_unlocked(aio, buffer: "\n", size: 1);
752#endif
753}
754
755void aio_write_newline(ASYNCIO *aio)
756{
757 aio_lock(aio);
758 aio_write_newline_unlocked(aio);
759 aio_unlock(aio);
760}
761
762int aio_error(ASYNCIO *aio)
763{
764 CLockScope ls(aio->lock);
765 return aio->error;
766}
767
768void aio_close(ASYNCIO *aio)
769{
770 {
771 CLockScope ls(aio->lock);
772 aio->finish = ASYNCIO_CLOSE;
773 }
774 sphore_signal(sem: &aio->sphore);
775}
776
777void aio_wait(ASYNCIO *aio)
778{
779 void *thread;
780 {
781 CLockScope ls(aio->lock);
782 thread = aio->thread;
783 aio->thread = nullptr;
784 if(aio->finish == ASYNCIO_RUNNING)
785 {
786 aio->finish = ASYNCIO_EXIT;
787 }
788 }
789 sphore_signal(sem: &aio->sphore);
790 thread_wait(thread);
791}
792
793void aio_free(ASYNCIO *aio)
794{
795 aio->lock.lock();
796 if(aio->thread)
797 {
798 thread_detach(thread: aio->thread);
799 aio->thread = nullptr;
800 }
801 aio_handle_free_and_unlock(aio);
802}
803
804struct THREAD_RUN
805{
806 void (*threadfunc)(void *);
807 void *u;
808};
809
810#if defined(CONF_FAMILY_UNIX)
811static void *thread_run(void *user)
812#elif defined(CONF_FAMILY_WINDOWS)
813static unsigned long __stdcall thread_run(void *user)
814#else
815#error not implemented
816#endif
817{
818#if defined(CONF_FAMILY_WINDOWS)
819 CWindowsComLifecycle WindowsComLifecycle(false);
820#endif
821 struct THREAD_RUN *data = (THREAD_RUN *)user;
822 void (*threadfunc)(void *) = data->threadfunc;
823 void *u = data->u;
824 free(ptr: data);
825 threadfunc(u);
826 return 0;
827}
828
829void *thread_init(void (*threadfunc)(void *), void *u, const char *name)
830{
831 struct THREAD_RUN *data = (THREAD_RUN *)malloc(size: sizeof(*data));
832 data->threadfunc = threadfunc;
833 data->u = u;
834#if defined(CONF_FAMILY_UNIX)
835 {
836 pthread_attr_t attr;
837 dbg_assert(pthread_attr_init(&attr) == 0, "pthread_attr_init failure");
838#if defined(CONF_PLATFORM_MACOS) && defined(__MAC_10_10) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_10
839 dbg_assert(pthread_attr_set_qos_class_np(&attr, QOS_CLASS_USER_INTERACTIVE, 0) == 0, "pthread_attr_set_qos_class_np failure");
840#endif
841 pthread_t id;
842 dbg_assert(pthread_create(&id, &attr, thread_run, data) == 0, "pthread_create failure");
843#if defined(CONF_PLATFORM_EMSCRIPTEN)
844 // Return control to the browser's main thread to allow the pthread to be started,
845 // otherwise we deadlock when waiting for a thread immediately after starting it.
846 emscripten_sleep(0);
847#endif
848 return (void *)id;
849 }
850#elif defined(CONF_FAMILY_WINDOWS)
851 HANDLE thread = CreateThread(nullptr, 0, thread_run, data, 0, nullptr);
852 dbg_assert(thread != nullptr, "CreateThread failure");
853 HMODULE kernel_base_handle;
854 if(GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, L"KernelBase.dll", &kernel_base_handle))
855 {
856 // Intentional
857#ifdef __MINGW32__
858#pragma GCC diagnostic push
859#pragma GCC diagnostic ignored "-Wcast-function-type"
860#endif
861 auto set_thread_description_function = reinterpret_cast<HRESULT(WINAPI *)(HANDLE, PCWSTR)>(GetProcAddress(kernel_base_handle, "SetThreadDescription"));
862#ifdef __MINGW32__
863#pragma GCC diagnostic pop
864#endif
865 if(set_thread_description_function)
866 set_thread_description_function(thread, windows_utf8_to_wide(name).c_str());
867 }
868 return thread;
869#else
870#error not implemented
871#endif
872}
873
874void thread_wait(void *thread)
875{
876#if defined(CONF_FAMILY_UNIX)
877 dbg_assert(pthread_join((pthread_t)thread, nullptr) == 0, "pthread_join failure");
878#elif defined(CONF_FAMILY_WINDOWS)
879 dbg_assert(WaitForSingleObject((HANDLE)thread, INFINITE) == WAIT_OBJECT_0, "WaitForSingleObject failure");
880 dbg_assert(CloseHandle(thread), "CloseHandle failure");
881#else
882#error not implemented
883#endif
884}
885
886void thread_yield()
887{
888#if defined(CONF_FAMILY_UNIX)
889 dbg_assert(sched_yield() == 0, "sched_yield failure");
890#elif defined(CONF_FAMILY_WINDOWS)
891 Sleep(0);
892#else
893#error not implemented
894#endif
895}
896
897void thread_detach(void *thread)
898{
899#if defined(CONF_FAMILY_UNIX)
900 dbg_assert(pthread_detach((pthread_t)thread) == 0, "pthread_detach failure");
901#elif defined(CONF_FAMILY_WINDOWS)
902 dbg_assert(CloseHandle(thread), "CloseHandle failure");
903#else
904#error not implemented
905#endif
906}
907
908void thread_init_and_detach(void (*threadfunc)(void *), void *u, const char *name)
909{
910 void *thread = thread_init(threadfunc, u, name);
911 thread_detach(thread);
912}
913
914#if defined(CONF_FAMILY_WINDOWS)
915void sphore_init(SEMAPHORE *sem)
916{
917 *sem = CreateSemaphoreW(nullptr, 0, std::numeric_limits<LONG>::max(), nullptr);
918 dbg_assert(*sem != nullptr, "CreateSemaphoreW failure");
919}
920void sphore_wait(SEMAPHORE *sem)
921{
922 dbg_assert(WaitForSingleObject((HANDLE)*sem, INFINITE) == WAIT_OBJECT_0, "WaitForSingleObject failure");
923}
924void sphore_signal(SEMAPHORE *sem)
925{
926 dbg_assert(ReleaseSemaphore((HANDLE)*sem, 1, nullptr), "ReleaseSemaphore failure");
927}
928void sphore_destroy(SEMAPHORE *sem)
929{
930 dbg_assert(CloseHandle((HANDLE)*sem), "CloseHandle failure");
931}
932#elif defined(CONF_PLATFORM_MACOS)
933void sphore_init(SEMAPHORE *sem)
934{
935 char aBuf[64];
936 str_format(aBuf, sizeof(aBuf), "/%d.%p", pid(), (void *)sem);
937 *sem = sem_open(aBuf, O_CREAT | O_EXCL, S_IRWXU | S_IRWXG, 0);
938 dbg_assert(*sem != SEM_FAILED, "sem_open failure, errno=%d, name='%s'", errno, aBuf);
939}
940void sphore_wait(SEMAPHORE *sem)
941{
942 while(true)
943 {
944 if(sem_wait(*sem) == 0)
945 break;
946 dbg_assert(errno == EINTR, "sem_wait failure");
947 }
948}
949void sphore_signal(SEMAPHORE *sem)
950{
951 dbg_assert(sem_post(*sem) == 0, "sem_post failure");
952}
953void sphore_destroy(SEMAPHORE *sem)
954{
955 dbg_assert(sem_close(*sem) == 0, "sem_close failure");
956 char aBuf[64];
957 str_format(aBuf, sizeof(aBuf), "/%d.%p", pid(), (void *)sem);
958 dbg_assert(sem_unlink(aBuf) == 0, "sem_unlink failure");
959}
960#elif defined(CONF_FAMILY_UNIX)
961void sphore_init(SEMAPHORE *sem)
962{
963 dbg_assert(sem_init(sem, 0, 0) == 0, "sem_init failure");
964}
965void sphore_wait(SEMAPHORE *sem)
966{
967 while(true)
968 {
969 if(sem_wait(sem: sem) == 0)
970 break;
971 dbg_assert(errno == EINTR, "sem_wait failure");
972 }
973}
974void sphore_signal(SEMAPHORE *sem)
975{
976 dbg_assert(sem_post(sem) == 0, "sem_post failure");
977}
978void sphore_destroy(SEMAPHORE *sem)
979{
980 dbg_assert(sem_destroy(sem) == 0, "sem_destroy failure");
981}
982#endif
983
984static int new_tick = -1;
985
986void set_new_tick()
987{
988 new_tick = 1;
989}
990
991/* ----- time ----- */
992static_assert(std::chrono::steady_clock::is_steady, "Compiler does not support steady clocks, it might be out of date.");
993static_assert(std::chrono::steady_clock::period::den / std::chrono::steady_clock::period::num >= 1000000000, "Compiler has a bad timer precision and might be out of date.");
994static const std::chrono::time_point<std::chrono::steady_clock> tw_start_time = std::chrono::steady_clock::now();
995
996int64_t time_get_impl()
997{
998 return std::chrono::duration_cast<std::chrono::nanoseconds>(d: std::chrono::steady_clock::now() - tw_start_time).count();
999}
1000
1001int64_t time_get()
1002{
1003 static int64_t last = 0;
1004 if(new_tick == 0)
1005 return last;
1006 if(new_tick != -1)
1007 new_tick = 0;
1008
1009 last = time_get_impl();
1010 return last;
1011}
1012
1013int64_t time_freq()
1014{
1015 using namespace std::chrono_literals;
1016 return std::chrono::nanoseconds(1s).count();
1017}
1018
1019/* ----- network ----- */
1020
1021const NETADDR NETADDR_ZEROED = {.type: NETTYPE_INVALID, .ip: {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, .port: 0};
1022
1023static void netaddr_to_sockaddr_in(const NETADDR *src, sockaddr_in *dest)
1024{
1025 dbg_assert((src->type & NETTYPE_IPV4) != 0, "Invalid address type '%d' for netaddr_to_sockaddr_in", src->type);
1026 mem_zero(block: dest, size: sizeof(*dest));
1027 dest->sin_family = AF_INET;
1028 dest->sin_port = htons(hostshort: src->port);
1029 mem_copy(dest: &dest->sin_addr.s_addr, source: src->ip, size: 4);
1030}
1031
1032static void netaddr_to_sockaddr_in6(const NETADDR *src, sockaddr_in6 *dest)
1033{
1034 dbg_assert((src->type & NETTYPE_IPV6) != 0, "Invalid address type '%d' for netaddr_to_sockaddr_in6", src->type);
1035 mem_zero(block: dest, size: sizeof(*dest));
1036 dest->sin6_family = AF_INET6;
1037 dest->sin6_port = htons(hostshort: src->port);
1038 mem_copy(dest: &dest->sin6_addr.s6_addr, source: src->ip, size: 16);
1039}
1040
1041static void sockaddr_to_netaddr(const sockaddr *src, socklen_t src_len, NETADDR *dst)
1042{
1043 *dst = NETADDR_ZEROED;
1044 if(src->sa_family == AF_INET && src_len >= (socklen_t)sizeof(sockaddr_in))
1045 {
1046 const sockaddr_in *src_in = (const sockaddr_in *)src;
1047 dst->type = NETTYPE_IPV4;
1048 dst->port = htons(hostshort: src_in->sin_port);
1049 static_assert(sizeof(dst->ip) >= sizeof(src_in->sin_addr.s_addr));
1050 mem_copy(dest: dst->ip, source: &src_in->sin_addr.s_addr, size: sizeof(src_in->sin_addr.s_addr));
1051 }
1052 else if(src->sa_family == AF_INET6 && src_len >= (socklen_t)sizeof(sockaddr_in6))
1053 {
1054 const sockaddr_in6 *src_in6 = (const sockaddr_in6 *)src;
1055 dst->type = NETTYPE_IPV6;
1056 dst->port = htons(hostshort: src_in6->sin6_port);
1057 static_assert(sizeof(dst->ip) >= sizeof(src_in6->sin6_addr.s6_addr));
1058 mem_copy(dest: dst->ip, source: &src_in6->sin6_addr.s6_addr, size: sizeof(src_in6->sin6_addr.s6_addr));
1059 }
1060 else
1061 {
1062 log_warn("net", "Cannot convert sockaddr of family %d", src->sa_family);
1063 }
1064}
1065
1066int net_addr_comp(const NETADDR *a, const NETADDR *b)
1067{
1068 int diff = a->type - b->type;
1069 if(diff != 0)
1070 {
1071 return diff;
1072 }
1073 diff = mem_comp(a: a->ip, b: b->ip, size: sizeof(a->ip));
1074 if(diff != 0)
1075 {
1076 return diff;
1077 }
1078 return a->port - b->port;
1079}
1080
1081bool NETADDR::operator==(const NETADDR &other) const
1082{
1083 return net_addr_comp(a: this, b: &other) == 0;
1084}
1085
1086bool NETADDR::operator!=(const NETADDR &other) const
1087{
1088 return net_addr_comp(a: this, b: &other) != 0;
1089}
1090
1091bool NETADDR::operator<(const NETADDR &other) const
1092{
1093 return net_addr_comp(a: this, b: &other) < 0;
1094}
1095
1096size_t std::hash<NETADDR>::operator()(const NETADDR &Addr) const noexcept
1097{
1098 size_t seed = std::hash<unsigned int>{}(Addr.type);
1099 seed ^= std::hash<std::string_view>{}(std::string_view(reinterpret_cast<const char *>(Addr.ip), sizeof(Addr.ip))) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
1100 seed ^= std::hash<unsigned short>{}(Addr.port) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
1101 return seed;
1102}
1103
1104int net_addr_comp_noport(const NETADDR *a, const NETADDR *b)
1105{
1106 int diff = a->type - b->type;
1107 if(diff != 0)
1108 {
1109 return diff;
1110 }
1111 return mem_comp(a: a->ip, b: b->ip, size: sizeof(a->ip));
1112}
1113
1114void net_addr_str_v6(const unsigned short ip[8], int port, char *buffer, int buffer_size)
1115{
1116 int longest_seq_len = 0;
1117 int longest_seq_start = -1;
1118 int w = 0;
1119 int i;
1120 {
1121 int seq_len = 0;
1122 int seq_start = -1;
1123 // Determine longest sequence of zeros.
1124 for(i = 0; i < 8 + 1; i++)
1125 {
1126 if(seq_start != -1)
1127 {
1128 if(i == 8 || ip[i] != 0)
1129 {
1130 if(longest_seq_len < seq_len)
1131 {
1132 longest_seq_len = seq_len;
1133 longest_seq_start = seq_start;
1134 }
1135 seq_len = 0;
1136 seq_start = -1;
1137 }
1138 else
1139 {
1140 seq_len += 1;
1141 }
1142 }
1143 else
1144 {
1145 if(i != 8 && ip[i] == 0)
1146 {
1147 seq_start = i;
1148 seq_len = 1;
1149 }
1150 }
1151 }
1152 }
1153 if(longest_seq_len <= 1)
1154 {
1155 longest_seq_len = 0;
1156 longest_seq_start = -1;
1157 }
1158 w += str_copy(dst: buffer + w, src: "[", dst_size: buffer_size - w);
1159 for(i = 0; i < 8; i++)
1160 {
1161 if(longest_seq_start <= i && i < longest_seq_start + longest_seq_len)
1162 {
1163 if(i == longest_seq_start)
1164 {
1165 w += str_copy(dst: buffer + w, src: "::", dst_size: buffer_size - w);
1166 }
1167 }
1168 else
1169 {
1170 const char *colon = (i == 0 || i == longest_seq_start + longest_seq_len) ? "" : ":";
1171 w += str_format(buffer: buffer + w, buffer_size: buffer_size - w, format: "%s%x", colon, ip[i]);
1172 }
1173 }
1174 w += str_copy(dst: buffer + w, src: "]", dst_size: buffer_size - w);
1175 if(port >= 0)
1176 {
1177 str_format(buffer: buffer + w, buffer_size: buffer_size - w, format: ":%d", port);
1178 }
1179}
1180
1181void net_addr_str(const NETADDR *addr, char *string, int max_length, bool add_port)
1182{
1183 if((addr->type & (NETTYPE_IPV4 | NETTYPE_WEBSOCKET_IPV4)) != 0)
1184 {
1185 if(add_port)
1186 {
1187 str_format(buffer: string, buffer_size: max_length, format: "%d.%d.%d.%d:%d", addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3], addr->port);
1188 }
1189 else
1190 {
1191 str_format(buffer: string, buffer_size: max_length, format: "%d.%d.%d.%d", addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3]);
1192 }
1193 }
1194 else if((addr->type & (NETTYPE_IPV6 | NETTYPE_WEBSOCKET_IPV6)) != 0)
1195 {
1196 unsigned short ip[8];
1197 for(int i = 0; i < 8; i++)
1198 {
1199 ip[i] = (addr->ip[i * 2] << 8) | (addr->ip[i * 2 + 1]);
1200 }
1201 int port = add_port ? addr->port : -1;
1202 net_addr_str_v6(ip, port, buffer: string, buffer_size: max_length);
1203 }
1204 else
1205 {
1206 dbg_assert_failed("unknown NETADDR type %d", addr->type);
1207 }
1208}
1209
1210static int priv_net_extract(const char *hostname, char *host, int max_host, int *port)
1211{
1212 *port = 0;
1213 host[0] = 0;
1214
1215 if(hostname[0] == '[')
1216 {
1217 // ipv6 mode
1218 int i;
1219 for(i = 1; i < max_host && hostname[i] && hostname[i] != ']'; i++)
1220 host[i - 1] = hostname[i];
1221 host[i - 1] = 0;
1222 if(hostname[i] != ']') // malformatted
1223 return -1;
1224
1225 i++;
1226 if(hostname[i] == ':')
1227 *port = str_toint(str: hostname + i + 1);
1228 }
1229 else
1230 {
1231 // generic mode (ipv4, hostname etc)
1232 int i;
1233 for(i = 0; i < max_host - 1 && hostname[i] && hostname[i] != ':'; i++)
1234 host[i] = hostname[i];
1235 host[i] = 0;
1236
1237 if(hostname[i] == ':')
1238 *port = str_toint(str: hostname + i + 1);
1239 }
1240
1241 return 0;
1242}
1243
1244static int net_host_lookup_fallback(const char *hostname, NETADDR *addr, int types, int port)
1245{
1246 if(str_comp_nocase(a: hostname, b: "localhost") == 0)
1247 {
1248 if(types == NETTYPE_IPV4)
1249 {
1250 dbg_assert(net_addr_from_str(addr, "127.0.0.1") == 0, "unreachable");
1251 addr->port = port;
1252 return 0;
1253 }
1254 else if(types == NETTYPE_IPV6)
1255 {
1256 dbg_assert(net_addr_from_str(addr, "[::1]") == 0, "unreachable");
1257 addr->port = port;
1258 return 0;
1259 }
1260 else
1261 {
1262 // TODO: return both IPv4 and IPv6 address
1263 dbg_assert(net_addr_from_str(addr, "127.0.0.1") == 0, "unreachable");
1264 addr->port = port;
1265 return 0;
1266 }
1267 }
1268 return -1;
1269}
1270
1271static int net_host_lookup_impl(const char *hostname, NETADDR *addr, int types)
1272{
1273 char host[256];
1274 int port = 0;
1275 if(priv_net_extract(hostname, host, max_host: sizeof(host), port: &port))
1276 return -1;
1277
1278 log_trace("host_lookup", "host='%s' port='%d' types='%d'", host, port, types);
1279
1280 struct addrinfo hints;
1281 mem_zero(block: &hints, size: sizeof(hints));
1282
1283 if(types == NETTYPE_IPV4)
1284 hints.ai_family = AF_INET;
1285 else if(types == NETTYPE_IPV6)
1286 hints.ai_family = AF_INET6;
1287 else
1288 hints.ai_family = AF_UNSPEC;
1289
1290 struct addrinfo *result = nullptr;
1291 int e = getaddrinfo(name: host, service: nullptr, req: &hints, pai: &result);
1292 if(!result)
1293 {
1294 return net_host_lookup_fallback(hostname, addr, types, port);
1295 }
1296
1297 if(e != 0)
1298 {
1299 freeaddrinfo(ai: result);
1300 return net_host_lookup_fallback(hostname, addr, types, port);
1301 }
1302
1303 sockaddr_to_netaddr(src: result->ai_addr, src_len: result->ai_addrlen, dst: addr);
1304 addr->port = port;
1305 freeaddrinfo(ai: result);
1306 return 0;
1307}
1308
1309int net_host_lookup(const char *hostname, NETADDR *addr, int types)
1310{
1311 const char *ws_hostname = str_startswith(str: hostname, prefix: "ws://");
1312 if(ws_hostname)
1313 {
1314 if((types & (NETTYPE_WEBSOCKET_IPV4 | NETTYPE_WEBSOCKET_IPV6)) == 0)
1315 {
1316 return -1;
1317 }
1318 int result = net_host_lookup_impl(hostname: ws_hostname, addr, types: types & ~(NETTYPE_WEBSOCKET_IPV4 | NETTYPE_WEBSOCKET_IPV6));
1319 if(result == 0)
1320 {
1321 if(addr->type == NETTYPE_IPV4)
1322 {
1323 addr->type = NETTYPE_WEBSOCKET_IPV4;
1324 }
1325 else if(addr->type == NETTYPE_IPV6)
1326 {
1327 addr->type = NETTYPE_WEBSOCKET_IPV6;
1328 }
1329 }
1330 return result;
1331 }
1332 return net_host_lookup_impl(hostname, addr, types: types & ~(NETTYPE_WEBSOCKET_IPV4 | NETTYPE_WEBSOCKET_IPV6));
1333}
1334
1335static int parse_int(int *out, const char **str)
1336{
1337 int i = 0;
1338 *out = 0;
1339 if(!str_isnum(c: **str))
1340 return -1;
1341
1342 i = **str - '0';
1343 (*str)++;
1344
1345 while(true)
1346 {
1347 if(!str_isnum(c: **str))
1348 {
1349 *out = i;
1350 return 0;
1351 }
1352
1353 i = (i * 10) + (**str - '0');
1354 (*str)++;
1355 }
1356
1357 return 0;
1358}
1359
1360static int parse_char(char c, const char **str)
1361{
1362 if(**str != c)
1363 return -1;
1364 (*str)++;
1365 return 0;
1366}
1367
1368static int parse_uint8(unsigned char *out, const char **str)
1369{
1370 int i;
1371 if(parse_int(out: &i, str) != 0)
1372 return -1;
1373 if(i < 0 || i > 0xff)
1374 return -1;
1375 *out = i;
1376 return 0;
1377}
1378
1379static int parse_uint16(unsigned short *out, const char **str)
1380{
1381 int i;
1382 if(parse_int(out: &i, str) != 0)
1383 return -1;
1384 if(i < 0 || i > 0xffff)
1385 return -1;
1386 *out = i;
1387 return 0;
1388}
1389
1390int net_addr_from_url(NETADDR *addr, const char *string, char *host_buf, size_t host_buf_size)
1391{
1392 bool sixup = false;
1393 mem_zero(block: addr, size: sizeof(*addr));
1394 const char *str = str_startswith(str: string, prefix: "tw-0.6+udp://");
1395 if(!str && (str = str_startswith(str: string, prefix: "tw-0.7+udp://")))
1396 {
1397 addr->type |= NETTYPE_TW7;
1398 sixup = true;
1399 }
1400 if(!str)
1401 return 1;
1402
1403 int length = str_length(str);
1404 int start = 0;
1405 int end = length;
1406 for(int i = 0; i < length; i++)
1407 {
1408 if(str[i] == '@')
1409 {
1410 if(start != 0)
1411 {
1412 // Two at signs.
1413 return true;
1414 }
1415 start = i + 1;
1416 }
1417 else if(str[i] == '/' || str[i] == '?' || str[i] == '#')
1418 {
1419 end = i;
1420 break;
1421 }
1422 }
1423
1424 char host[128];
1425 str_truncate(dst: host, dst_size: sizeof(host), src: str + start, truncation_len: end - start);
1426 if(host_buf)
1427 str_copy(dst: host_buf, src: host, dst_size: host_buf_size);
1428
1429 int failure = net_addr_from_str(addr, string: host);
1430 if(failure)
1431 return failure;
1432
1433 if(sixup)
1434 addr->type |= NETTYPE_TW7;
1435
1436 return failure;
1437}
1438
1439bool net_addr_is_local(const NETADDR *addr)
1440{
1441 char addr_str[NETADDR_MAXSTRSIZE];
1442 net_addr_str(addr, string: addr_str, max_length: sizeof(addr_str), add_port: true);
1443
1444 if(addr->ip[0] == 127 || addr->ip[0] == 10 || (addr->ip[0] == 192 && addr->ip[1] == 168) || (addr->ip[0] == 172 && (addr->ip[1] >= 16 && addr->ip[1] <= 31)))
1445 return true;
1446
1447 if(str_startswith(str: addr_str, prefix: "[fe80:") || str_startswith(str: addr_str, prefix: "[::1"))
1448 return true;
1449
1450 return false;
1451}
1452
1453int net_addr_from_str(NETADDR *addr, const char *string)
1454{
1455 const char *str = string;
1456 mem_zero(block: addr, size: sizeof(NETADDR));
1457
1458 if(str[0] == '[')
1459 {
1460 /* ipv6 */
1461 sockaddr_in6 sa6;
1462 char buf[128];
1463 int i;
1464 str++;
1465 for(i = 0; i < 127 && str[i] && str[i] != ']'; i++)
1466 buf[i] = str[i];
1467 buf[i] = 0;
1468 str += i;
1469#if defined(CONF_FAMILY_WINDOWS)
1470 {
1471 int size;
1472 sa6.sin6_family = AF_INET6;
1473 size = (int)sizeof(sa6);
1474 if(WSAStringToAddressA(buf, AF_INET6, nullptr, (sockaddr *)&sa6, &size) != 0)
1475 return -1;
1476 }
1477#else
1478 sa6.sin6_family = AF_INET6;
1479
1480 if(inet_pton(AF_INET6, cp: buf, buf: &sa6.sin6_addr) != 1)
1481 return -1;
1482#endif
1483 sockaddr_to_netaddr(src: (sockaddr *)&sa6, src_len: sizeof(sa6), dst: addr);
1484
1485 if(*str == ']')
1486 {
1487 str++;
1488 if(*str == ':')
1489 {
1490 str++;
1491 if(parse_uint16(out: &addr->port, str: &str))
1492 return -1;
1493 }
1494 else
1495 {
1496 addr->port = 0;
1497 }
1498 }
1499 else
1500 return -1;
1501
1502 return 0;
1503 }
1504 else
1505 {
1506 /* ipv4 */
1507 if(parse_uint8(out: &addr->ip[0], str: &str))
1508 return -1;
1509 if(parse_char(c: '.', str: &str))
1510 return -1;
1511 if(parse_uint8(out: &addr->ip[1], str: &str))
1512 return -1;
1513 if(parse_char(c: '.', str: &str))
1514 return -1;
1515 if(parse_uint8(out: &addr->ip[2], str: &str))
1516 return -1;
1517 if(parse_char(c: '.', str: &str))
1518 return -1;
1519 if(parse_uint8(out: &addr->ip[3], str: &str))
1520 return -1;
1521 if(*str == ':')
1522 {
1523 str++;
1524 if(parse_uint16(out: &addr->port, str: &str))
1525 return -1;
1526 }
1527 if(*str != '\0')
1528 return -1;
1529
1530 addr->type = NETTYPE_IPV4;
1531 }
1532
1533 return 0;
1534}
1535
1536static void priv_net_close_socket(int sock)
1537{
1538#if defined(CONF_FAMILY_WINDOWS)
1539 dbg_assert(closesocket(sock) == 0, "closesocket failure (%s)", net_error_message().c_str());
1540#else
1541 dbg_assert(close(sock) == 0, "close failure (%s)", net_error_message().c_str());
1542#endif
1543}
1544
1545static void priv_net_close_all_sockets(NETSOCKET sock)
1546{
1547 if(sock->ipv4sock >= 0)
1548 {
1549 priv_net_close_socket(sock: sock->ipv4sock);
1550 sock->ipv4sock = -1;
1551 sock->type &= ~NETTYPE_IPV4;
1552 }
1553
1554#if defined(CONF_WEBSOCKETS)
1555 if(sock->web_ipv4sock >= 0)
1556 {
1557 websocket_destroy(socket: sock->web_ipv4sock);
1558 sock->web_ipv4sock = -1;
1559 sock->type &= ~NETTYPE_WEBSOCKET_IPV4;
1560 }
1561#endif
1562
1563 if(sock->ipv6sock >= 0)
1564 {
1565 priv_net_close_socket(sock: sock->ipv6sock);
1566 sock->ipv6sock = -1;
1567 sock->type &= ~NETTYPE_IPV6;
1568 }
1569
1570#if defined(CONF_WEBSOCKETS)
1571 if(sock->web_ipv6sock >= 0)
1572 {
1573 websocket_destroy(socket: sock->web_ipv6sock);
1574 sock->web_ipv6sock = -1;
1575 sock->type &= ~NETTYPE_WEBSOCKET_IPV6;
1576 }
1577#endif
1578
1579 free(ptr: sock);
1580}
1581
1582static int priv_net_create_socket(int domain, int type, const NETADDR *bindaddr)
1583{
1584 int sock = socket(domain: domain, type: type, protocol: 0);
1585 if(sock < 0)
1586 {
1587 log_error("net", "Failed to create socket with domain %d and type %d (%s)", domain, type, net_error_message().c_str());
1588 return -1;
1589 }
1590
1591#if defined(CONF_FAMILY_UNIX)
1592 // On TCP sockets set SO_REUSEADDR to fix port rebind on restart
1593 if(domain == AF_INET && type == SOCK_STREAM)
1594 {
1595 int reuse_addr = 1;
1596 if(setsockopt(fd: sock, SOL_SOCKET, SO_REUSEADDR, optval: (const char *)&reuse_addr, optlen: sizeof(reuse_addr)) != 0)
1597 {
1598 log_error("net", "Setting SO_REUSEADDR failed with domain %d and type %d (%s)", domain, type, net_error_message().c_str());
1599 }
1600 }
1601#elif defined(CONF_FAMILY_WINDOWS)
1602 {
1603 // Ensure exclusive use of address, otherwise it's possible on Windows to bind to the same address and port with another socket.
1604 // See https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse (last update 06/14/2022)
1605 int exclusive_addr_use = 1;
1606 if(setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const char *)&exclusive_addr_use, sizeof(exclusive_addr_use)) != 0)
1607 {
1608 log_error("net", "Setting SO_EXCLUSIVEADDRUSE failed with domain %d and type %d (%s)", domain, type, net_error_message().c_str());
1609 }
1610 }
1611#endif
1612
1613 // Set to IPv6-only if that's what we are creating, to ensure that dual-stack does not block the same IPv4 port.
1614#if defined(IPV6_V6ONLY)
1615 if(domain == AF_INET6)
1616 {
1617 int ipv6only = 1;
1618 if(setsockopt(fd: sock, IPPROTO_IPV6, IPV6_V6ONLY, optval: (const char *)&ipv6only, optlen: sizeof(ipv6only)) != 0)
1619 {
1620 log_error("net", "Setting IPV6_V6ONLY failed with domain %d and type %d (%s)", domain, type, net_error_message().c_str());
1621 }
1622 }
1623#endif
1624
1625 sockaddr_storage addr;
1626 socklen_t addr_len;
1627 if(bindaddr->type == NETTYPE_IPV4)
1628 {
1629 netaddr_to_sockaddr_in(src: bindaddr, dest: (sockaddr_in *)&addr);
1630 addr_len = sizeof(sockaddr_in);
1631 }
1632 else if(bindaddr->type == NETTYPE_IPV6)
1633 {
1634 netaddr_to_sockaddr_in6(src: bindaddr, dest: (sockaddr_in6 *)&addr);
1635 addr_len = sizeof(sockaddr_in6);
1636 }
1637 else
1638 {
1639 dbg_assert_failed("socket type invalid: %d", type);
1640 }
1641
1642 if(bind(fd: sock, addr: (sockaddr *)&addr, len: addr_len) != 0)
1643 {
1644 log_error("net", "Failed to bind socket with domain %d and type %d (%s)", domain, type, net_error_message().c_str());
1645 priv_net_close_socket(sock);
1646 return -1;
1647 }
1648
1649 return sock;
1650}
1651
1652int net_socket_type(NETSOCKET sock)
1653{
1654 return sock->type;
1655}
1656
1657NETSOCKET net_udp_create(NETADDR bindaddr)
1658{
1659 NETSOCKET sock = (NETSOCKET_INTERNAL *)malloc(size: sizeof(*sock));
1660 *sock = invalid_socket;
1661
1662 if(bindaddr.type & NETTYPE_IPV4)
1663 {
1664 NETADDR bindaddr_ipv4 = bindaddr;
1665 bindaddr_ipv4.type = NETTYPE_IPV4;
1666 const int socket = priv_net_create_socket(AF_INET, SOCK_DGRAM, bindaddr: &bindaddr_ipv4);
1667 if(socket >= 0)
1668 {
1669 sock->type |= NETTYPE_IPV4;
1670 sock->ipv4sock = socket;
1671
1672 // Set broadcast
1673 {
1674 int broadcast = 1;
1675 if(setsockopt(fd: socket, SOL_SOCKET, SO_BROADCAST, optval: (const char *)&broadcast, optlen: sizeof(broadcast)) != 0)
1676 {
1677 log_error("net", "Setting SO_BROADCAST on IPv4 failed (%s)", net_error_message().c_str());
1678 }
1679 }
1680
1681 // Set DSCP/TOS
1682 {
1683 int iptos = 0x10; // IPTOS_LOWDELAY
1684 if(setsockopt(fd: socket, IPPROTO_IP, IP_TOS, optval: (const char *)&iptos, optlen: sizeof(iptos)) != 0)
1685 {
1686 log_error("net", "Setting IP_TOS on IPv4 failed (%s)", net_error_message().c_str());
1687 }
1688 }
1689 }
1690 }
1691
1692#if defined(CONF_WEBSOCKETS)
1693 if(bindaddr.type & NETTYPE_WEBSOCKET_IPV4)
1694 {
1695 NETADDR bindaddr_websocket_ipv4 = bindaddr;
1696 bindaddr_websocket_ipv4.type = NETTYPE_WEBSOCKET_IPV4;
1697 const int socket = websocket_create(bindaddr: &bindaddr_websocket_ipv4);
1698 if(socket >= 0)
1699 {
1700 sock->type |= NETTYPE_WEBSOCKET_IPV4;
1701 sock->web_ipv4sock = socket;
1702 }
1703 }
1704#endif
1705
1706 if(bindaddr.type & NETTYPE_IPV6)
1707 {
1708 NETADDR bindaddr_ipv6 = bindaddr;
1709 bindaddr_ipv6.type = NETTYPE_IPV6;
1710 const int socket = priv_net_create_socket(AF_INET6, SOCK_DGRAM, bindaddr: &bindaddr_ipv6);
1711 if(socket >= 0)
1712 {
1713 sock->type |= NETTYPE_IPV6;
1714 sock->ipv6sock = socket;
1715
1716 // Set broadcast
1717 {
1718 int broadcast = 1;
1719 if(setsockopt(fd: socket, SOL_SOCKET, SO_BROADCAST, optval: (const char *)&broadcast, optlen: sizeof(broadcast)) != 0)
1720 {
1721 log_error("net", "Setting SO_BROADCAST on IPv6 failed (%s)", net_error_message().c_str());
1722 }
1723 }
1724
1725 // Set DSCP/TOS
1726 // TODO: setting IP_TOS on ipv6 with setsockopt is not supported on Windows, see https://github.com/ddnet/ddnet/issues/7605
1727#if !defined(CONF_FAMILY_WINDOWS)
1728 {
1729 int iptos = 0x10; // IPTOS_LOWDELAY
1730 if(setsockopt(fd: socket, IPPROTO_IP, IP_TOS, optval: (const char *)&iptos, optlen: sizeof(iptos)) != 0)
1731 {
1732 log_error("net", "Setting IP_TOS on IPv6 failed (%s)", net_error_message().c_str());
1733 }
1734 }
1735#endif
1736 }
1737 }
1738
1739#if defined(CONF_WEBSOCKETS)
1740 if(bindaddr.type & NETTYPE_WEBSOCKET_IPV6)
1741 {
1742 NETADDR bindaddr_websocket_ipv6 = bindaddr;
1743 bindaddr_websocket_ipv6.type = NETTYPE_WEBSOCKET_IPV6;
1744 const int socket = websocket_create(bindaddr: &bindaddr_websocket_ipv6);
1745 if(socket >= 0)
1746 {
1747 sock->type |= NETTYPE_WEBSOCKET_IPV6;
1748 sock->web_ipv6sock = socket;
1749 }
1750 }
1751#endif
1752
1753 if(sock->type == NETTYPE_INVALID)
1754 {
1755 free(ptr: sock);
1756 sock = nullptr;
1757 }
1758 else
1759 {
1760 net_set_non_blocking(sock);
1761 net_buffer_init(buffer: &sock->buffer);
1762 }
1763
1764 return sock;
1765}
1766
1767int net_udp_send(NETSOCKET sock, const NETADDR *addr, const void *data, int size)
1768{
1769 int d = -1;
1770
1771 if(addr->type & NETTYPE_IPV4)
1772 {
1773 if(sock->ipv4sock >= 0)
1774 {
1775 sockaddr_in sa;
1776 if(addr->type & NETTYPE_LINK_BROADCAST)
1777 {
1778 mem_zero(block: &sa, size: sizeof(sa));
1779 sa.sin_port = htons(hostshort: addr->port);
1780 sa.sin_family = AF_INET;
1781 sa.sin_addr.s_addr = INADDR_BROADCAST;
1782 }
1783 else
1784 {
1785 netaddr_to_sockaddr_in(src: addr, dest: &sa);
1786 }
1787
1788 d = sendto(fd: sock->ipv4sock, buf: (const char *)data, n: size, flags: 0, addr: (sockaddr *)&sa, addr_len: sizeof(sa));
1789 }
1790 else
1791 {
1792 log_error("net", "Cannot send IPv4 traffic to this socket");
1793 }
1794 }
1795
1796#if defined(CONF_WEBSOCKETS)
1797 if(addr->type & NETTYPE_WEBSOCKET_IPV4)
1798 {
1799 if(sock->web_ipv4sock >= 0)
1800 {
1801 if(addr->type & NETTYPE_LINK_BROADCAST)
1802 {
1803 log_error("net", "Cannot send broadcasts to Websocket IPv4");
1804 }
1805 else
1806 {
1807 d = websocket_send(socket: sock->web_ipv4sock, data: (const unsigned char *)data, size, addr);
1808 }
1809 }
1810 else
1811 {
1812 log_error("net", "Cannot send Websocket IPv4 traffic to this socket");
1813 }
1814 }
1815#endif
1816
1817 if(addr->type & NETTYPE_IPV6)
1818 {
1819 if(sock->ipv6sock >= 0)
1820 {
1821 sockaddr_in6 sa;
1822 if(addr->type & NETTYPE_LINK_BROADCAST)
1823 {
1824 mem_zero(block: &sa, size: sizeof(sa));
1825 sa.sin6_port = htons(hostshort: addr->port);
1826 sa.sin6_family = AF_INET6;
1827 sa.sin6_addr.s6_addr[0] = 0xff; /* multicast */
1828 sa.sin6_addr.s6_addr[1] = 0x02; /* link local scope */
1829 sa.sin6_addr.s6_addr[15] = 1; /* all nodes */
1830 }
1831 else
1832 {
1833 netaddr_to_sockaddr_in6(src: addr, dest: &sa);
1834 }
1835
1836 d = sendto(fd: sock->ipv6sock, buf: (const char *)data, n: size, flags: 0, addr: (sockaddr *)&sa, addr_len: sizeof(sa));
1837 }
1838 else
1839 {
1840 log_error("net", "Cannot send IPv6 traffic to this socket");
1841 }
1842 }
1843
1844#if defined(CONF_WEBSOCKETS)
1845 if(addr->type & NETTYPE_WEBSOCKET_IPV6)
1846 {
1847 if(sock->web_ipv6sock >= 0)
1848 {
1849 if(addr->type & NETTYPE_LINK_BROADCAST)
1850 {
1851 log_error("net", "Cannot send broadcasts to Websocket IPv6");
1852 }
1853 else
1854 {
1855 d = websocket_send(socket: sock->web_ipv6sock, data: (const unsigned char *)data, size, addr);
1856 }
1857 }
1858 else
1859 {
1860 log_error("net", "Cannot send Websocket IPv6 traffic to this socket");
1861 }
1862 }
1863#endif
1864
1865 network_stats.sent_bytes += size;
1866 network_stats.sent_packets++;
1867 return d;
1868}
1869
1870void net_buffer_init(NETSOCKET_BUFFER *buffer)
1871{
1872#if defined(CONF_PLATFORM_LINUX)
1873 buffer->pos = 0;
1874 buffer->size = 0;
1875 mem_zero(block: buffer->msgs, size: sizeof(buffer->msgs));
1876 mem_zero(block: buffer->iovecs, size: sizeof(buffer->iovecs));
1877 mem_zero(block: buffer->sockaddrs, size: sizeof(buffer->sockaddrs));
1878 for(int i = 0; i < VLEN; ++i)
1879 {
1880 buffer->iovecs[i].iov_base = buffer->bufs[i];
1881 buffer->iovecs[i].iov_len = PACKETSIZE;
1882 buffer->msgs[i].msg_hdr.msg_iov = &(buffer->iovecs[i]);
1883 buffer->msgs[i].msg_hdr.msg_iovlen = 1;
1884 buffer->msgs[i].msg_hdr.msg_name = &(buffer->sockaddrs[i]);
1885 buffer->msgs[i].msg_hdr.msg_namelen = sizeof(buffer->sockaddrs[i]);
1886 }
1887#endif
1888}
1889
1890void net_buffer_reinit(NETSOCKET_BUFFER *buffer)
1891{
1892#if defined(CONF_PLATFORM_LINUX)
1893 for(int i = 0; i < VLEN; i++)
1894 {
1895 buffer->msgs[i].msg_hdr.msg_namelen = sizeof(buffer->sockaddrs[i]);
1896 }
1897#endif
1898}
1899
1900void net_buffer_simple(NETSOCKET_BUFFER *buffer, char **buf, int *size)
1901{
1902#if defined(CONF_PLATFORM_LINUX)
1903 *buf = buffer->bufs[0];
1904 *size = sizeof(buffer->bufs[0]);
1905#else
1906 *buf = buffer->buf;
1907 *size = sizeof(buffer->buf);
1908#endif
1909}
1910
1911int net_udp_recv(NETSOCKET sock, NETADDR *addr, unsigned char **data)
1912{
1913 static const auto &&update_stats = [](int bytes) {
1914 network_stats.recv_bytes += bytes;
1915 network_stats.recv_packets++;
1916 };
1917
1918 int bytes = 0;
1919#if defined(CONF_PLATFORM_LINUX)
1920 if(sock->ipv4sock >= 0)
1921 {
1922 if(sock->buffer.pos >= sock->buffer.size)
1923 {
1924 net_buffer_reinit(buffer: &sock->buffer);
1925 sock->buffer.size = recvmmsg(fd: sock->ipv4sock, vmessages: sock->buffer.msgs, VLEN, flags: 0, NULL);
1926 sock->buffer.pos = 0;
1927 }
1928 }
1929
1930 if(sock->ipv6sock >= 0)
1931 {
1932 if(sock->buffer.pos >= sock->buffer.size)
1933 {
1934 net_buffer_reinit(buffer: &sock->buffer);
1935 sock->buffer.size = recvmmsg(fd: sock->ipv6sock, vmessages: sock->buffer.msgs, VLEN, flags: 0, NULL);
1936 sock->buffer.pos = 0;
1937 }
1938 }
1939
1940 if(sock->buffer.pos < sock->buffer.size)
1941 {
1942 sockaddr_to_netaddr(src: (sockaddr *)&(sock->buffer.sockaddrs[sock->buffer.pos]), src_len: sizeof(sock->buffer.sockaddrs[sock->buffer.pos]), dst: addr);
1943 bytes = sock->buffer.msgs[sock->buffer.pos].msg_len;
1944 *data = (unsigned char *)sock->buffer.bufs[sock->buffer.pos];
1945 sock->buffer.pos++;
1946 update_stats(bytes);
1947 return bytes;
1948 }
1949#else
1950 if(sock->ipv4sock >= 0)
1951 {
1952 sockaddr_storage recv_addr;
1953 socklen_t fromlen = sizeof(recv_addr);
1954 bytes = recvfrom(sock->ipv4sock, sock->buffer.buf, sizeof(sock->buffer.buf), 0, (sockaddr *)&recv_addr, &fromlen);
1955 *data = (unsigned char *)sock->buffer.buf;
1956 if(bytes > 0)
1957 {
1958 sockaddr_to_netaddr((sockaddr *)&recv_addr, fromlen, addr);
1959 update_stats(bytes);
1960 return bytes;
1961 }
1962 }
1963
1964 if(sock->ipv6sock >= 0)
1965 {
1966 sockaddr_storage recv_addr;
1967 socklen_t fromlen = sizeof(recv_addr);
1968 bytes = recvfrom(sock->ipv6sock, sock->buffer.buf, sizeof(sock->buffer.buf), 0, (sockaddr *)&recv_addr, &fromlen);
1969 *data = (unsigned char *)sock->buffer.buf;
1970 if(bytes > 0)
1971 {
1972 sockaddr_to_netaddr((sockaddr *)&recv_addr, fromlen, addr);
1973 update_stats(bytes);
1974 return bytes;
1975 }
1976 }
1977#endif
1978
1979#if defined(CONF_WEBSOCKETS)
1980 if(sock->web_ipv4sock >= 0)
1981 {
1982 char *buf;
1983 int size;
1984 net_buffer_simple(buffer: &sock->buffer, buf: &buf, size: &size);
1985 bytes = websocket_recv(socket: sock->web_ipv4sock, data: (unsigned char *)buf, maxsize: size, addr);
1986 *data = (unsigned char *)buf;
1987 if(bytes > 0)
1988 {
1989 update_stats(bytes);
1990 return bytes;
1991 }
1992 }
1993
1994 if(sock->web_ipv6sock >= 0)
1995 {
1996 char *buf;
1997 int size;
1998 net_buffer_simple(buffer: &sock->buffer, buf: &buf, size: &size);
1999 bytes = websocket_recv(socket: sock->web_ipv6sock, data: (unsigned char *)buf, maxsize: size, addr);
2000 *data = (unsigned char *)buf;
2001 if(bytes > 0)
2002 {
2003 update_stats(bytes);
2004 return bytes;
2005 }
2006 }
2007#endif
2008
2009 return bytes < 0 ? -1 : 0;
2010}
2011
2012void net_udp_close(NETSOCKET sock)
2013{
2014 priv_net_close_all_sockets(sock);
2015}
2016
2017NETSOCKET net_tcp_create(NETADDR bindaddr)
2018{
2019 NETSOCKET sock = (NETSOCKET_INTERNAL *)malloc(size: sizeof(*sock));
2020 *sock = invalid_socket;
2021
2022 if(bindaddr.type & NETTYPE_IPV4)
2023 {
2024 NETADDR bindaddr_ipv4 = bindaddr;
2025 bindaddr_ipv4.type = NETTYPE_IPV4;
2026 const int socket4 = priv_net_create_socket(AF_INET, SOCK_STREAM, bindaddr: &bindaddr_ipv4);
2027 if(socket4 >= 0)
2028 {
2029 sock->type |= NETTYPE_IPV4;
2030 sock->ipv4sock = socket4;
2031 }
2032 }
2033
2034 if(bindaddr.type & NETTYPE_IPV6)
2035 {
2036 NETADDR bindaddr_ipv6 = bindaddr;
2037 bindaddr_ipv6.type = NETTYPE_IPV6;
2038 const int socket6 = priv_net_create_socket(AF_INET6, SOCK_STREAM, bindaddr: &bindaddr_ipv6);
2039 if(socket6 >= 0)
2040 {
2041 sock->type |= NETTYPE_IPV6;
2042 sock->ipv6sock = socket6;
2043 }
2044 }
2045
2046 if(sock->type == NETTYPE_INVALID)
2047 {
2048 free(ptr: sock);
2049 sock = nullptr;
2050 }
2051
2052 return sock;
2053}
2054
2055static int net_set_blocking_impl(NETSOCKET sock, bool blocking)
2056{
2057 unsigned long mode = blocking ? 0 : 1;
2058 const char *mode_str = blocking ? "blocking" : "non-blocking";
2059 int sockets[] = {sock->ipv4sock, sock->ipv6sock};
2060 const char *socket_str[] = {"IPv4", "IPv6"};
2061
2062 for(size_t i = 0; i < std::size(sockets); ++i)
2063 {
2064 if(sockets[i] >= 0)
2065 {
2066#if defined(CONF_FAMILY_WINDOWS)
2067 if(ioctlsocket(sockets[i], FIONBIO, (unsigned long *)&mode) != NO_ERROR)
2068 {
2069 log_error("net", "Setting %s mode for %s socket failed (%s)", socket_str[i], mode_str, net_error_message().c_str());
2070 }
2071#else
2072 if(ioctl(fd: sockets[i], FIONBIO, (unsigned long *)&mode) == -1)
2073 {
2074 log_error("net", "Setting %s mode for %s socket failed (%s)", socket_str[i], mode_str, net_error_message().c_str());
2075 }
2076#endif
2077 }
2078 }
2079
2080 return 0;
2081}
2082
2083int net_set_non_blocking(NETSOCKET sock)
2084{
2085 return net_set_blocking_impl(sock, blocking: false);
2086}
2087
2088int net_set_blocking(NETSOCKET sock)
2089{
2090 return net_set_blocking_impl(sock, blocking: true);
2091}
2092
2093int net_tcp_listen(NETSOCKET sock, int backlog)
2094{
2095 int err = -1;
2096 if(sock->ipv4sock >= 0)
2097 {
2098 err = listen(fd: sock->ipv4sock, n: backlog);
2099 }
2100 if(sock->ipv6sock >= 0)
2101 {
2102 err = listen(fd: sock->ipv6sock, n: backlog);
2103 }
2104 return err;
2105}
2106
2107int net_tcp_accept(NETSOCKET sock, NETSOCKET *new_sock, NETADDR *a)
2108{
2109 *new_sock = nullptr;
2110
2111 if(sock->ipv4sock >= 0)
2112 {
2113 sockaddr_storage addr;
2114 socklen_t sockaddr_len = sizeof(addr);
2115
2116 int s = accept(fd: sock->ipv4sock, addr: (sockaddr *)&addr, addr_len: &sockaddr_len);
2117 if(s != -1)
2118 {
2119 sockaddr_to_netaddr(src: (sockaddr *)&addr, src_len: sockaddr_len, dst: a);
2120
2121 *new_sock = (NETSOCKET_INTERNAL *)malloc(size: sizeof(**new_sock));
2122 **new_sock = invalid_socket;
2123 (*new_sock)->type = NETTYPE_IPV4;
2124 (*new_sock)->ipv4sock = s;
2125 return s;
2126 }
2127 }
2128
2129 if(sock->ipv6sock >= 0)
2130 {
2131 sockaddr_storage addr;
2132 socklen_t sockaddr_len = sizeof(addr);
2133
2134 int s = accept(fd: sock->ipv6sock, addr: (sockaddr *)&addr, addr_len: &sockaddr_len);
2135 if(s != -1)
2136 {
2137 *new_sock = (NETSOCKET_INTERNAL *)malloc(size: sizeof(**new_sock));
2138 **new_sock = invalid_socket;
2139 sockaddr_to_netaddr(src: (sockaddr *)&addr, src_len: sockaddr_len, dst: a);
2140 (*new_sock)->type = NETTYPE_IPV6;
2141 (*new_sock)->ipv6sock = s;
2142 return s;
2143 }
2144 }
2145
2146 return -1;
2147}
2148
2149int net_tcp_connect(NETSOCKET sock, const NETADDR *a)
2150{
2151 if(a->type & NETTYPE_IPV4)
2152 {
2153 if(sock->ipv4sock < 0)
2154 return -2;
2155 sockaddr_in addr;
2156 netaddr_to_sockaddr_in(src: a, dest: &addr);
2157 return connect(fd: sock->ipv4sock, addr: (sockaddr *)&addr, len: sizeof(addr));
2158 }
2159
2160 if(a->type & NETTYPE_IPV6)
2161 {
2162 if(sock->ipv6sock < 0)
2163 return -2;
2164 sockaddr_in6 addr;
2165 netaddr_to_sockaddr_in6(src: a, dest: &addr);
2166 return connect(fd: sock->ipv6sock, addr: (sockaddr *)&addr, len: sizeof(addr));
2167 }
2168
2169 return -1;
2170}
2171
2172int net_tcp_connect_non_blocking(NETSOCKET sock, NETADDR bindaddr)
2173{
2174 net_set_non_blocking(sock);
2175 int res = net_tcp_connect(sock, a: &bindaddr);
2176 net_set_blocking(sock);
2177 return res;
2178}
2179
2180int net_tcp_send(NETSOCKET sock, const void *data, int size)
2181{
2182 int bytes = -1;
2183
2184 if(sock->ipv4sock >= 0)
2185 {
2186 bytes = send(fd: sock->ipv4sock, buf: (const char *)data, n: size, flags: 0);
2187 }
2188 if(sock->ipv6sock >= 0)
2189 {
2190 bytes = send(fd: sock->ipv6sock, buf: (const char *)data, n: size, flags: 0);
2191 }
2192
2193 return bytes;
2194}
2195
2196int net_tcp_recv(NETSOCKET sock, void *data, int maxsize)
2197{
2198 int bytes = -1;
2199
2200 if(sock->ipv4sock >= 0)
2201 {
2202 bytes = recv(fd: sock->ipv4sock, buf: (char *)data, n: maxsize, flags: 0);
2203 }
2204 if(sock->ipv6sock >= 0)
2205 {
2206 bytes = recv(fd: sock->ipv6sock, buf: (char *)data, n: maxsize, flags: 0);
2207 }
2208
2209 return bytes;
2210}
2211
2212void net_tcp_close(NETSOCKET sock)
2213{
2214 priv_net_close_all_sockets(sock);
2215}
2216
2217int net_errno()
2218{
2219#if defined(CONF_FAMILY_WINDOWS)
2220 return WSAGetLastError();
2221#else
2222 return errno;
2223#endif
2224}
2225
2226std::string net_error_message()
2227{
2228 const int error = net_errno();
2229#if defined(CONF_FAMILY_WINDOWS)
2230 const std::string message = windows_format_system_message(error);
2231 return std::to_string(error) + " '" + message + "'";
2232#else
2233 return std::to_string(val: error) + " '" + strerror(errnum: error) + "'";
2234#endif
2235}
2236
2237int net_would_block()
2238{
2239#if defined(CONF_FAMILY_WINDOWS)
2240 return net_errno() == WSAEWOULDBLOCK;
2241#else
2242 return net_errno() == EWOULDBLOCK;
2243#endif
2244}
2245
2246void net_init()
2247{
2248#if defined(CONF_FAMILY_WINDOWS)
2249 WSADATA wsa_data;
2250 dbg_assert(WSAStartup(MAKEWORD(1, 1), &wsa_data) == 0, "WSAStartup failure");
2251#endif
2252#if defined(CONF_WEBSOCKETS)
2253 websocket_init();
2254#endif
2255}
2256
2257#if defined(CONF_FAMILY_UNIX)
2258UNIXSOCKET net_unix_create_unnamed()
2259{
2260 return socket(AF_UNIX, SOCK_DGRAM, protocol: 0);
2261}
2262
2263int net_unix_send(UNIXSOCKET sock, UNIXSOCKETADDR *addr, void *data, int size)
2264{
2265 return sendto(fd: sock, buf: data, n: size, flags: 0, addr: (sockaddr *)addr, addr_len: sizeof(*addr));
2266}
2267
2268void net_unix_set_addr(UNIXSOCKETADDR *addr, const char *path)
2269{
2270 mem_zero(block: addr, size: sizeof(*addr));
2271 addr->sun_family = AF_UNIX;
2272 str_copy(dst&: addr->sun_path, src: path);
2273}
2274
2275void net_unix_close(UNIXSOCKET sock)
2276{
2277 close(fd: sock);
2278}
2279#endif
2280
2281#if defined(CONF_FAMILY_WINDOWS)
2282static inline time_t filetime_to_unixtime(LPFILETIME filetime)
2283{
2284 time_t t;
2285 ULARGE_INTEGER li;
2286 li.LowPart = filetime->dwLowDateTime;
2287 li.HighPart = filetime->dwHighDateTime;
2288
2289 li.QuadPart /= 10000000; // 100ns to 1s
2290 li.QuadPart -= 11644473600LL; // Windows epoch is in the past
2291
2292 t = li.QuadPart;
2293 return t == (time_t)li.QuadPart ? t : (time_t)-1;
2294}
2295#endif
2296
2297void fs_listdir(const char *dir, FS_LISTDIR_CALLBACK cb, int type, void *user)
2298{
2299#if defined(CONF_FAMILY_WINDOWS)
2300 char buffer[IO_MAX_PATH_LENGTH];
2301 str_format(buffer, sizeof(buffer), "%s/*", dir);
2302 const std::wstring wide_buffer = windows_utf8_to_wide(buffer);
2303
2304 WIN32_FIND_DATAW finddata;
2305 HANDLE handle = FindFirstFileW(wide_buffer.c_str(), &finddata);
2306 if(handle == INVALID_HANDLE_VALUE)
2307 return;
2308
2309 do
2310 {
2311 const std::optional<std::string> current_entry = windows_wide_to_utf8(finddata.cFileName);
2312 if(!current_entry.has_value())
2313 {
2314 log_error("filesystem", "ERROR: file/folder name containing invalid UTF-16 found in folder '%s'", dir);
2315 continue;
2316 }
2317 if(cb(current_entry.value().c_str(), (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0, type, user))
2318 break;
2319 } while(FindNextFileW(handle, &finddata));
2320
2321 FindClose(handle);
2322#else
2323 DIR *dir_handle = opendir(name: dir);
2324 if(dir_handle == nullptr)
2325 return;
2326
2327 char buffer[IO_MAX_PATH_LENGTH];
2328 str_format(buffer, buffer_size: sizeof(buffer), format: "%s/", dir);
2329 size_t length = str_length(str: buffer);
2330 while(true)
2331 {
2332 struct dirent *entry = readdir(dirp: dir_handle);
2333 if(entry == nullptr)
2334 break;
2335 if(!str_utf8_check(str: entry->d_name))
2336 {
2337 log_error("filesystem", "ERROR: file/folder name containing invalid UTF-8 found in folder '%s'", dir);
2338 continue;
2339 }
2340 str_copy(dst: buffer + length, src: entry->d_name, dst_size: sizeof(buffer) - length);
2341 if(cb(entry->d_name, fs_is_dir(path: buffer), type, user))
2342 break;
2343 }
2344
2345 closedir(dirp: dir_handle);
2346#endif
2347}
2348
2349void fs_listdir_fileinfo(const char *dir, FS_LISTDIR_CALLBACK_FILEINFO cb, int type, void *user)
2350{
2351#if defined(CONF_FAMILY_WINDOWS)
2352 char buffer[IO_MAX_PATH_LENGTH];
2353 str_format(buffer, sizeof(buffer), "%s/*", dir);
2354 const std::wstring wide_buffer = windows_utf8_to_wide(buffer);
2355
2356 WIN32_FIND_DATAW finddata;
2357 HANDLE handle = FindFirstFileW(wide_buffer.c_str(), &finddata);
2358 if(handle == INVALID_HANDLE_VALUE)
2359 return;
2360
2361 do
2362 {
2363 const std::optional<std::string> current_entry = windows_wide_to_utf8(finddata.cFileName);
2364 if(!current_entry.has_value())
2365 {
2366 log_error("filesystem", "ERROR: file/folder name containing invalid UTF-16 found in folder '%s'", dir);
2367 continue;
2368 }
2369
2370 CFsFileInfo info;
2371 info.m_pName = current_entry.value().c_str();
2372 info.m_TimeCreated = filetime_to_unixtime(&finddata.ftCreationTime);
2373 info.m_TimeModified = filetime_to_unixtime(&finddata.ftLastWriteTime);
2374
2375 if(cb(&info, (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0, type, user))
2376 break;
2377 } while(FindNextFileW(handle, &finddata));
2378
2379 FindClose(handle);
2380#else
2381 DIR *dir_handle = opendir(name: dir);
2382 if(dir_handle == nullptr)
2383 return;
2384
2385 char buffer[IO_MAX_PATH_LENGTH];
2386 str_format(buffer, buffer_size: sizeof(buffer), format: "%s/", dir);
2387 size_t length = str_length(str: buffer);
2388
2389 while(true)
2390 {
2391 struct dirent *entry = readdir(dirp: dir_handle);
2392 if(entry == nullptr)
2393 break;
2394 if(!str_utf8_check(str: entry->d_name))
2395 {
2396 log_error("filesystem", "ERROR: file/folder name containing invalid UTF-8 found in folder '%s'", dir);
2397 continue;
2398 }
2399 str_copy(dst: buffer + length, src: entry->d_name, dst_size: sizeof(buffer) - length);
2400 time_t created = -1, modified = -1;
2401 fs_file_time(name: buffer, created: &created, modified: &modified);
2402
2403 CFsFileInfo info;
2404 info.m_pName = entry->d_name;
2405 info.m_TimeCreated = created;
2406 info.m_TimeModified = modified;
2407
2408 if(cb(&info, fs_is_dir(path: buffer), type, user))
2409 break;
2410 }
2411
2412 closedir(dirp: dir_handle);
2413#endif
2414}
2415
2416int fs_storage_path(const char *appname, char *path, int max)
2417{
2418#if defined(CONF_FAMILY_WINDOWS)
2419 WCHAR *wide_home = nullptr;
2420 if(SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, /* current user */ nullptr, &wide_home) != S_OK)
2421 {
2422 log_error("filesystem", "ERROR: could not determine location of Roaming/AppData folder");
2423 CoTaskMemFree(wide_home);
2424 path[0] = '\0';
2425 return -1;
2426 }
2427 const std::optional<std::string> home = windows_wide_to_utf8(wide_home);
2428 CoTaskMemFree(wide_home);
2429 if(!home.has_value())
2430 {
2431 log_error("filesystem", "ERROR: path of Roaming/AppData folder contains invalid UTF-16");
2432 path[0] = '\0';
2433 return -1;
2434 }
2435 str_copy(path, home.value().c_str(), max);
2436 fs_normalize_path(path);
2437 str_append(path, "/", max);
2438 str_append(path, appname, max);
2439 return 0;
2440#else
2441 char *home = getenv(name: "HOME");
2442 if(!home)
2443 {
2444 path[0] = '\0';
2445 return -1;
2446 }
2447
2448 if(!str_utf8_check(str: home))
2449 {
2450 log_error("filesystem", "ERROR: the HOME environment variable contains invalid UTF-8");
2451 path[0] = '\0';
2452 return -1;
2453 }
2454
2455#if defined(CONF_PLATFORM_HAIKU)
2456 str_format(path, max, "%s/config/settings/%s", home, appname);
2457#elif defined(CONF_PLATFORM_MACOS)
2458 str_format(path, max, "%s/Library/Application Support/%s", home, appname);
2459#else
2460 if(str_comp(a: appname, b: "Teeworlds") == 0)
2461 {
2462 // fallback for old directory for Teeworlds compatibility
2463 str_format(buffer: path, buffer_size: max, format: "%s/.%s", home, appname);
2464 }
2465 else
2466 {
2467 char *data_home = getenv(name: "XDG_DATA_HOME");
2468 if(data_home)
2469 {
2470 if(!str_utf8_check(str: data_home))
2471 {
2472 log_error("filesystem", "ERROR: the XDG_DATA_HOME environment variable contains invalid UTF-8");
2473 path[0] = '\0';
2474 return -1;
2475 }
2476 str_format(buffer: path, buffer_size: max, format: "%s/%s", data_home, appname);
2477 }
2478 else
2479 str_format(buffer: path, buffer_size: max, format: "%s/.local/share/%s", home, appname);
2480 }
2481 for(int i = str_length(str: path) - str_length(str: appname); path[i]; i++)
2482 path[i] = tolower(c: (unsigned char)path[i]);
2483#endif
2484
2485 return 0;
2486#endif
2487}
2488
2489int fs_makedir_rec_for(const char *path)
2490{
2491 char buffer[IO_MAX_PATH_LENGTH];
2492 str_copy(dst&: buffer, src: path);
2493 for(int index = 1; buffer[index] != '\0'; ++index)
2494 {
2495 // Do not try to create folder for drive letters on Windows,
2496 // as this is not necessary and may fail for system drives.
2497 if((buffer[index] == '/' || buffer[index] == '\\') && buffer[index + 1] != '\0' && buffer[index - 1] != ':')
2498 {
2499 buffer[index] = '\0';
2500 if(fs_makedir(path: buffer) < 0)
2501 {
2502 return -1;
2503 }
2504 buffer[index] = '/';
2505 }
2506 }
2507 return 0;
2508}
2509
2510int fs_is_file(const char *path)
2511{
2512#if defined(CONF_FAMILY_WINDOWS)
2513 const std::wstring wide_path = windows_utf8_to_wide(path);
2514 DWORD attributes = GetFileAttributesW(wide_path.c_str());
2515 return attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0;
2516#else
2517 struct stat sb;
2518 if(stat(file: path, buf: &sb) == -1)
2519 return 0;
2520 return S_ISREG(sb.st_mode) ? 1 : 0;
2521#endif
2522}
2523
2524int fs_is_dir(const char *path)
2525{
2526#if defined(CONF_FAMILY_WINDOWS)
2527 const std::wstring wide_path = windows_utf8_to_wide(path);
2528 DWORD attributes = GetFileAttributesW(wide_path.c_str());
2529 return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0;
2530#else
2531 struct stat sb;
2532 if(stat(file: path, buf: &sb) == -1)
2533 return 0;
2534 return S_ISDIR(sb.st_mode) ? 1 : 0;
2535#endif
2536}
2537
2538int fs_is_relative_path(const char *path)
2539{
2540#if defined(CONF_FAMILY_WINDOWS)
2541 const std::wstring wide_path = windows_utf8_to_wide(path);
2542 return PathIsRelativeW(wide_path.c_str()) ? 1 : 0;
2543#else
2544 return path[0] == '/' ? 0 : 1; // yes, it's that simple
2545#endif
2546}
2547
2548int fs_chdir(const char *path)
2549{
2550#if defined(CONF_FAMILY_WINDOWS)
2551 const std::wstring wide_path = windows_utf8_to_wide(path);
2552 return SetCurrentDirectoryW(wide_path.c_str()) != 0 ? 0 : 1;
2553#else
2554 return chdir(path: path) ? 1 : 0;
2555#endif
2556}
2557
2558char *fs_getcwd(char *buffer, int buffer_size)
2559{
2560#if defined(CONF_FAMILY_WINDOWS)
2561 const DWORD size_needed = GetCurrentDirectoryW(0, nullptr);
2562 std::wstring wide_current_dir(size_needed, L'0');
2563 dbg_assert(GetCurrentDirectoryW(size_needed, wide_current_dir.data()) == size_needed - 1, "GetCurrentDirectoryW failure");
2564 const std::optional<std::string> current_dir = windows_wide_to_utf8(wide_current_dir.c_str());
2565 if(!current_dir.has_value())
2566 {
2567 buffer[0] = '\0';
2568 return nullptr;
2569 }
2570 str_copy(buffer, current_dir.value().c_str(), buffer_size);
2571 fs_normalize_path(buffer);
2572 return buffer;
2573#else
2574 char *result = getcwd(buf: buffer, size: buffer_size);
2575 if(result == nullptr || !str_utf8_check(str: result))
2576 {
2577 buffer[0] = '\0';
2578 return nullptr;
2579 }
2580 return result;
2581#endif
2582}
2583
2584const char *fs_filename(const char *path)
2585{
2586 for(const char *filename = path + str_length(str: path); filename >= path; --filename)
2587 {
2588 if(filename[0] == '/' || filename[0] == '\\')
2589 return filename + 1;
2590 }
2591 return path;
2592}
2593
2594void fs_split_file_extension(const char *filename, char *name, size_t name_size, char *extension, size_t extension_size)
2595{
2596 dbg_assert(name != nullptr || extension != nullptr, "name or extension parameter required");
2597 dbg_assert(name == nullptr || name_size > 0, "name_size invalid");
2598 dbg_assert(extension == nullptr || extension_size > 0, "extension_size invalid");
2599
2600 const char *last_dot = str_rchr(haystack: filename, needle: '.');
2601 if(last_dot == nullptr || last_dot == filename)
2602 {
2603 if(extension != nullptr)
2604 extension[0] = '\0';
2605 if(name != nullptr)
2606 str_copy(dst: name, src: filename, dst_size: name_size);
2607 }
2608 else
2609 {
2610 if(extension != nullptr)
2611 str_copy(dst: extension, src: last_dot + 1, dst_size: extension_size);
2612 if(name != nullptr)
2613 str_truncate(dst: name, dst_size: name_size, src: filename, truncation_len: last_dot - filename);
2614 }
2615}
2616
2617void fs_normalize_path(char *path)
2618{
2619 for(int i = 0; path[i] != '\0';)
2620 {
2621 if(path[i] == '\\')
2622 {
2623 path[i] = '/';
2624 }
2625 if(i > 0 && path[i] == '/' && path[i + 1] == '\0')
2626 {
2627 path[i] = '\0';
2628 --i;
2629 }
2630 else
2631 {
2632 ++i;
2633 }
2634 }
2635}
2636
2637int fs_parent_dir(char *path)
2638{
2639 char *parent = nullptr;
2640 for(; *path; ++path)
2641 {
2642 if(*path == '/' || *path == '\\')
2643 parent = path;
2644 }
2645
2646 if(parent)
2647 {
2648 *parent = 0;
2649 return 0;
2650 }
2651 return 1;
2652}
2653
2654int fs_remove(const char *filename)
2655{
2656#if defined(CONF_FAMILY_WINDOWS)
2657 if(fs_is_dir(filename))
2658 {
2659 // Not great, but otherwise using this function on a folder would only rename the folder but fail to delete it.
2660 return 1;
2661 }
2662 const std::wstring wide_filename = windows_utf8_to_wide(filename);
2663
2664 unsigned random_num = secure_rand();
2665 std::wstring wide_filename_temp;
2666 do
2667 {
2668 char suffix[64];
2669 str_format(suffix, sizeof(suffix), ".%08X.toberemoved", random_num);
2670 wide_filename_temp = wide_filename + windows_utf8_to_wide(suffix);
2671 ++random_num;
2672 } while(GetFileAttributesW(wide_filename_temp.c_str()) != INVALID_FILE_ATTRIBUTES);
2673
2674 // The DeleteFileW function only marks the file for deletion but the deletion may not take effect immediately, which can
2675 // cause subsequent operations using this filename to fail until all handles are closed. The MoveFileExW function with the
2676 // MOVEFILE_WRITE_THROUGH flag is guaranteed to wait for the file to be moved on disk, so we first rename the file to be
2677 // deleted to a random temporary name and then mark that for deletion, to ensure that the filename is usable immediately.
2678 if(MoveFileExW(wide_filename.c_str(), wide_filename_temp.c_str(), MOVEFILE_WRITE_THROUGH) == 0)
2679 {
2680 const DWORD error = GetLastError();
2681 if(error == ERROR_FILE_NOT_FOUND)
2682 {
2683 return 0; // Success: Renaming failed because the original file did not exist.
2684 }
2685 const std::string filename_temp = windows_wide_to_utf8(wide_filename_temp.c_str()).value_or("(invalid filename)");
2686 log_error("filesystem", "Failed to rename file '%s' to '%s' for removal (%ld '%s')", filename, filename_temp.c_str(), error, windows_format_system_message(error).c_str());
2687 return 1;
2688 }
2689 if(DeleteFileW(wide_filename_temp.c_str()) != 0)
2690 {
2691 return 0; // Success: Marked the renamed file for deletion successfully.
2692 }
2693 const DWORD error = GetLastError();
2694 if(error == ERROR_FILE_NOT_FOUND)
2695 {
2696 return 0; // Success: Another process deleted the renamed file we were about to delete?!
2697 }
2698 const std::string filename_temp = windows_wide_to_utf8(wide_filename_temp.c_str()).value_or("(invalid filename)");
2699 log_error("filesystem", "Failed to remove file '%s' (%ld '%s')", filename_temp.c_str(), error, windows_format_system_message(error).c_str());
2700 // Success: While the temporary could not be deleted, this is also considered success because the original file does not exist anymore.
2701 // Callers of this function expect that the original file does not exist anymore if and only if the function succeeded.
2702 return 0;
2703#else
2704 if(unlink(name: filename) == 0 || errno == ENOENT)
2705 {
2706 return 0;
2707 }
2708 log_error("filesystem", "Failed to remove file '%s' (%d '%s')", filename, errno, strerror(errno));
2709 return 1;
2710#endif
2711}
2712
2713int fs_rename(const char *oldname, const char *newname)
2714{
2715#if defined(CONF_FAMILY_WINDOWS)
2716 const std::wstring wide_oldname = windows_utf8_to_wide(oldname);
2717 const std::wstring wide_newname = windows_utf8_to_wide(newname);
2718 if(MoveFileExW(wide_oldname.c_str(), wide_newname.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH) != 0)
2719 {
2720 return 0;
2721 }
2722 const DWORD error = GetLastError();
2723 log_error("filesystem", "Failed to rename file '%s' to '%s' (%ld '%s')", oldname, newname, error, windows_format_system_message(error).c_str());
2724 return 1;
2725#else
2726 if(rename(old: oldname, new: newname) == 0)
2727 {
2728 return 0;
2729 }
2730 log_error("filesystem", "Failed to rename file '%s' to '%s' (%d '%s')", oldname, newname, errno, strerror(errno));
2731 return 1;
2732#endif
2733}
2734
2735int fs_file_time(const char *name, time_t *created, time_t *modified)
2736{
2737#if defined(CONF_FAMILY_WINDOWS)
2738 WIN32_FIND_DATAW finddata;
2739 const std::wstring wide_name = windows_utf8_to_wide(name);
2740 HANDLE handle = FindFirstFileW(wide_name.c_str(), &finddata);
2741 if(handle == INVALID_HANDLE_VALUE)
2742 return 1;
2743
2744 *created = filetime_to_unixtime(&finddata.ftCreationTime);
2745 *modified = filetime_to_unixtime(&finddata.ftLastWriteTime);
2746 FindClose(handle);
2747#elif defined(CONF_FAMILY_UNIX)
2748 struct stat sb;
2749 if(stat(file: name, buf: &sb))
2750 return 1;
2751
2752 *created = sb.st_ctime;
2753 *modified = sb.st_mtime;
2754#else
2755#error not implemented
2756#endif
2757
2758 return 0;
2759}
2760
2761void swap_endian(void *data, unsigned elem_size, unsigned num)
2762{
2763 char *src = (char *)data;
2764 char *dst = src + (elem_size - 1);
2765
2766 while(num)
2767 {
2768 unsigned n = elem_size >> 1;
2769 char tmp;
2770 while(n)
2771 {
2772 tmp = *src;
2773 *src = *dst;
2774 *dst = tmp;
2775
2776 src++;
2777 dst--;
2778 n--;
2779 }
2780
2781 src = src + (elem_size >> 1);
2782 dst = src + (elem_size - 1);
2783 num--;
2784 }
2785}
2786
2787int net_socket_read_wait(NETSOCKET sock, std::chrono::nanoseconds nanoseconds)
2788{
2789 const int64_t microseconds = std::chrono::duration_cast<std::chrono::microseconds>(d: nanoseconds).count();
2790 dbg_assert(microseconds >= 0, "Negative wait duration %" PRId64 " not allowed", microseconds);
2791
2792 fd_set readfds;
2793 FD_ZERO(&readfds);
2794
2795 int maxfd = -1;
2796 if(sock->ipv4sock >= 0)
2797 {
2798 FD_SET(sock->ipv4sock, &readfds);
2799 maxfd = sock->ipv4sock;
2800 }
2801 if(sock->ipv6sock >= 0)
2802 {
2803 FD_SET(sock->ipv6sock, &readfds);
2804 maxfd = std::max(a: maxfd, b: sock->ipv6sock);
2805 }
2806#if defined(CONF_WEBSOCKETS)
2807 if(sock->web_ipv4sock >= 0)
2808 {
2809 maxfd = std::max(a: maxfd, b: websocket_fd_set(socket: sock->web_ipv4sock, set: &readfds));
2810 }
2811 if(sock->web_ipv6sock >= 0)
2812 {
2813 maxfd = std::max(a: maxfd, b: websocket_fd_set(socket: sock->web_ipv6sock, set: &readfds));
2814 }
2815#endif
2816 if(maxfd < 0)
2817 {
2818 return 0;
2819 }
2820
2821 struct timeval tv;
2822 tv.tv_sec = microseconds / 1000000;
2823 tv.tv_usec = microseconds % 1000000;
2824 // don't care about writefds and exceptfds
2825 select(nfds: maxfd + 1, readfds: &readfds, writefds: nullptr, exceptfds: nullptr, timeout: &tv);
2826
2827 if(sock->ipv4sock >= 0 && FD_ISSET(sock->ipv4sock, &readfds))
2828 {
2829 return 1;
2830 }
2831 if(sock->ipv6sock >= 0 && FD_ISSET(sock->ipv6sock, &readfds))
2832 {
2833 return 1;
2834 }
2835#if defined(CONF_WEBSOCKETS)
2836 if(sock->web_ipv4sock >= 0 && websocket_fd_get(socket: sock->web_ipv4sock, set: &readfds))
2837 {
2838 return 1;
2839 }
2840 if(sock->web_ipv6sock >= 0 && websocket_fd_get(socket: sock->web_ipv6sock, set: &readfds))
2841 {
2842 return 1;
2843 }
2844#endif
2845 return 0;
2846}
2847
2848int64_t time_timestamp()
2849{
2850 return time(timer: nullptr);
2851}
2852
2853static tm time_localtime_threadlocal(time_t *time_data)
2854{
2855#if defined(CONF_FAMILY_WINDOWS)
2856 // The result of localtime is thread-local on Windows
2857 // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-localtime32-localtime64
2858 tm *time = localtime(time_data);
2859#else
2860 // Thread-local buffer for the result of localtime_r
2861 thread_local tm time_info_buf;
2862 tm *time = localtime_r(timer: time_data, tp: &time_info_buf);
2863#endif
2864 dbg_assert(time != nullptr, "Failed to get local time for time data %" PRId64, (int64_t)time_data);
2865 return *time;
2866}
2867
2868int time_houroftheday()
2869{
2870 time_t time_data;
2871 time(timer: &time_data);
2872 const tm time_info = time_localtime_threadlocal(time_data: &time_data);
2873 return time_info.tm_hour;
2874}
2875
2876static bool time_iseasterday(time_t time_data, tm time_info)
2877{
2878 // compute Easter day (Sunday) using https://en.wikipedia.org/w/index.php?title=Computus&oldid=890710285#Anonymous_Gregorian_algorithm
2879 int Y = time_info.tm_year + 1900;
2880 int a = Y % 19;
2881 int b = Y / 100;
2882 int c = Y % 100;
2883 int d = b / 4;
2884 int e = b % 4;
2885 int f = (b + 8) / 25;
2886 int g = (b - f + 1) / 3;
2887 int h = (19 * a + b - d - g + 15) % 30;
2888 int i = c / 4;
2889 int k = c % 4;
2890 int L = (32 + 2 * e + 2 * i - h - k) % 7;
2891 int m = (a + 11 * h + 22 * L) / 451;
2892 int month = (h + L - 7 * m + 114) / 31;
2893 int day = ((h + L - 7 * m + 114) % 31) + 1;
2894
2895 // (now-1d ≤ easter ≤ now+2d) <=> (easter-2d ≤ now ≤ easter+1d) <=> (Good Friday ≤ now ≤ Easter Monday)
2896 for(int day_offset = -1; day_offset <= 2; day_offset++)
2897 {
2898 time_data = time_data + day_offset * 60 * 60 * 24;
2899 const tm offset_time_info = time_localtime_threadlocal(time_data: &time_data);
2900 if(offset_time_info.tm_mon == month - 1 && offset_time_info.tm_mday == day)
2901 return true;
2902 }
2903 return false;
2904}
2905
2906ETimeSeason time_season()
2907{
2908 time_t time_data;
2909 time(timer: &time_data);
2910 const tm time_info = time_localtime_threadlocal(time_data: &time_data);
2911
2912 if((time_info.tm_mon == 11 && time_info.tm_mday == 31) || (time_info.tm_mon == 0 && time_info.tm_mday == 1))
2913 {
2914 return SEASON_NEWYEAR;
2915 }
2916 else if(time_info.tm_mon == 11 && time_info.tm_mday >= 24 && time_info.tm_mday <= 26)
2917 {
2918 return SEASON_XMAS;
2919 }
2920 else if((time_info.tm_mon == 9 && time_info.tm_mday == 31) || (time_info.tm_mon == 10 && time_info.tm_mday == 1))
2921 {
2922 return SEASON_HALLOWEEN;
2923 }
2924 else if(time_iseasterday(time_data, time_info))
2925 {
2926 return SEASON_EASTER;
2927 }
2928
2929 switch(time_info.tm_mon)
2930 {
2931 case 11:
2932 case 0:
2933 case 1:
2934 return SEASON_WINTER;
2935 case 2:
2936 case 3:
2937 case 4:
2938 return SEASON_SPRING;
2939 case 5:
2940 case 6:
2941 case 7:
2942 return SEASON_SUMMER;
2943 case 8:
2944 case 9:
2945 case 10:
2946 return SEASON_AUTUMN;
2947 default:
2948 dbg_assert_failed("Invalid month: %d", time_info.tm_mon);
2949 }
2950}
2951
2952int str_format_v(char *buffer, int buffer_size, const char *format, va_list args)
2953{
2954#if defined(CONF_FAMILY_WINDOWS)
2955 _vsprintf_p(buffer, buffer_size, format, args);
2956 buffer[buffer_size - 1] = 0; /* assure null termination */
2957#else
2958 vsnprintf(s: buffer, maxlen: buffer_size, format: format, arg: args);
2959 /* null termination is assured by definition of vsnprintf */
2960#endif
2961 return str_utf8_fix_truncation(str: buffer);
2962}
2963
2964#if !defined(CONF_DEBUG)
2965int str_format_int(char *buffer, size_t buffer_size, int value)
2966{
2967 buffer[0] = '\0'; // Fix false positive clang-analyzer-core.UndefinedBinaryOperatorResult when using result
2968 auto result = std::to_chars(buffer, buffer + buffer_size - 1, value);
2969 result.ptr[0] = '\0';
2970 return result.ptr - buffer;
2971}
2972#endif
2973
2974#undef str_format
2975int str_format(char *buffer, int buffer_size, const char *format, ...)
2976{
2977 va_list args;
2978 va_start(args, format);
2979 int length = str_format_v(buffer, buffer_size, format, args);
2980 va_end(args);
2981 return length;
2982}
2983#if !defined(CONF_DEBUG)
2984#define str_format str_format_opt
2985#endif
2986
2987static int min3(int a, int b, int c)
2988{
2989 int min = a;
2990 if(b < min)
2991 min = b;
2992 if(c < min)
2993 min = c;
2994 return min;
2995}
2996
2997int str_utf8_dist(const char *a, const char *b)
2998{
2999 int buf_len = 2 * (str_length(str: a) + 1 + str_length(str: b) + 1);
3000 int *buf = (int *)calloc(nmemb: buf_len, size: sizeof(*buf));
3001 int result = str_utf8_dist_buffer(a, b, buf, buf_len);
3002 free(ptr: buf);
3003 return result;
3004}
3005
3006static int str_to_utf32_unchecked(const char *str, int **out)
3007{
3008 int out_len = 0;
3009 while((**out = str_utf8_decode(ptr: &str)))
3010 {
3011 (*out)++;
3012 out_len++;
3013 }
3014 return out_len;
3015}
3016
3017int str_utf32_dist_buffer(const int *a, int a_len, const int *b, int b_len, int *buf, int buf_len)
3018{
3019 int i, j;
3020 dbg_assert(buf_len >= (a_len + 1) + (b_len + 1), "buffer too small");
3021 if(a_len > b_len)
3022 {
3023 int tmp1 = a_len;
3024 const int *tmp2 = a;
3025
3026 a_len = b_len;
3027 a = b;
3028
3029 b_len = tmp1;
3030 b = tmp2;
3031 }
3032#define B(i, j) buf[((j) & 1) * (a_len + 1) + (i)]
3033 for(i = 0; i <= a_len; i++)
3034 {
3035 B(i, 0) = i;
3036 }
3037 for(j = 1; j <= b_len; j++)
3038 {
3039 B(0, j) = j;
3040 for(i = 1; i <= a_len; i++)
3041 {
3042 int subst = (a[i - 1] != b[j - 1]);
3043 B(i, j) = min3(
3044 B(i - 1, j) + 1,
3045 B(i, j - 1) + 1,
3046 B(i - 1, j - 1) + subst);
3047 }
3048 }
3049 return B(a_len, b_len);
3050#undef B
3051}
3052
3053int str_utf8_dist_buffer(const char *a_utf8, const char *b_utf8, int *buf, int buf_len)
3054{
3055 int a_utf8_len = str_length(str: a_utf8);
3056 int b_utf8_len = str_length(str: b_utf8);
3057 int *a, *b; // UTF-32
3058 int a_len, b_len; // UTF-32 length
3059 dbg_assert(buf_len >= 2 * (a_utf8_len + 1 + b_utf8_len + 1), "buffer too small");
3060 if(a_utf8_len > b_utf8_len)
3061 {
3062 const char *tmp2 = a_utf8;
3063 a_utf8 = b_utf8;
3064 b_utf8 = tmp2;
3065 }
3066 a = buf;
3067 a_len = str_to_utf32_unchecked(str: a_utf8, out: &buf);
3068 b = buf;
3069 b_len = str_to_utf32_unchecked(str: b_utf8, out: &buf);
3070 return str_utf32_dist_buffer(a, a_len, b, b_len, buf, buf_len: buf_len - b_len - a_len);
3071}
3072
3073#ifdef __GNUC__
3074#pragma GCC diagnostic push
3075#pragma GCC diagnostic ignored "-Wformat-nonliteral"
3076#endif
3077void str_timestamp_ex(time_t time_data, char *buffer, int buffer_size, const char *format)
3078{
3079 const tm time_info = time_localtime_threadlocal(time_data: &time_data);
3080 strftime(s: buffer, maxsize: buffer_size, format: format, tp: &time_info);
3081 buffer[buffer_size - 1] = 0; /* assure null termination */
3082}
3083
3084void str_timestamp_format(char *buffer, int buffer_size, const char *format)
3085{
3086 time_t time_data;
3087 time(timer: &time_data);
3088 str_timestamp_ex(time_data, buffer, buffer_size, format);
3089}
3090
3091void str_timestamp(char *buffer, int buffer_size)
3092{
3093 str_timestamp_format(buffer, buffer_size, FORMAT_NOSPACE);
3094}
3095
3096bool timestamp_from_str(const char *string, const char *format, time_t *timestamp)
3097{
3098 std::tm tm{};
3099 std::istringstream ss(string);
3100 ss >> std::get_time(tmb: &tm, fmt: format);
3101 if(ss.fail() || !ss.eof())
3102 return false;
3103
3104 time_t result = mktime(tp: &tm);
3105 if(result < 0)
3106 return false;
3107
3108 *timestamp = result;
3109 return true;
3110}
3111#ifdef __GNUC__
3112#pragma GCC diagnostic pop
3113#endif
3114
3115int str_time(int64_t centisecs, int format, char *buffer, int buffer_size)
3116{
3117 const int sec = 100;
3118 const int min = 60 * sec;
3119 const int hour = 60 * min;
3120 const int day = 24 * hour;
3121
3122 if(buffer_size <= 0)
3123 return -1;
3124
3125 if(centisecs < 0)
3126 centisecs = 0;
3127
3128 buffer[0] = 0;
3129
3130 switch(format)
3131 {
3132 case TIME_DAYS:
3133 if(centisecs >= day)
3134 return str_format(buffer, buffer_size, "%" PRId64 "d %02" PRId64 ":%02" PRId64 ":%02" PRId64, centisecs / day,
3135 (centisecs % day) / hour, (centisecs % hour) / min, (centisecs % min) / sec);
3136 [[fallthrough]];
3137 case TIME_HOURS:
3138 if(centisecs >= hour)
3139 return str_format(buffer, buffer_size, "%02" PRId64 ":%02" PRId64 ":%02" PRId64, centisecs / hour,
3140 (centisecs % hour) / min, (centisecs % min) / sec);
3141 [[fallthrough]];
3142 case TIME_MINS:
3143 return str_format(buffer, buffer_size, "%02" PRId64 ":%02" PRId64, centisecs / min,
3144 (centisecs % min) / sec);
3145 case TIME_HOURS_CENTISECS:
3146 if(centisecs >= hour)
3147 return str_format(buffer, buffer_size, "%02" PRId64 ":%02" PRId64 ":%02" PRId64 ".%02" PRId64, centisecs / hour,
3148 (centisecs % hour) / min, (centisecs % min) / sec, centisecs % sec);
3149 [[fallthrough]];
3150 case TIME_MINS_CENTISECS:
3151 if(centisecs >= min)
3152 return str_format(buffer, buffer_size, "%02" PRId64 ":%02" PRId64 ".%02" PRId64, centisecs / min,
3153 (centisecs % min) / sec, centisecs % sec);
3154 [[fallthrough]];
3155 case TIME_SECS_CENTISECS:
3156 return str_format(buffer, buffer_size, "%02" PRId64 ".%02" PRId64, (centisecs % min) / sec, centisecs % sec);
3157 }
3158
3159 return -1;
3160}
3161
3162int str_time_float(float secs, int format, char *buffer, int buffer_size)
3163{
3164 return str_time(centisecs: llroundf(x: secs * 1000) / 10, format, buffer, buffer_size);
3165}
3166
3167void net_stats(NETSTATS *stats_inout)
3168{
3169 *stats_inout = network_stats;
3170}
3171
3172static_assert(sizeof(unsigned) == 4, "unsigned must be 4 bytes in size");
3173static_assert(sizeof(unsigned) == sizeof(int), "unsigned and int must have the same size");
3174
3175unsigned bytes_be_to_uint(const unsigned char *bytes)
3176{
3177 return ((bytes[0] & 0xffu) << 24u) | ((bytes[1] & 0xffu) << 16u) | ((bytes[2] & 0xffu) << 8u) | (bytes[3] & 0xffu);
3178}
3179
3180void uint_to_bytes_be(unsigned char *bytes, unsigned value)
3181{
3182 bytes[0] = (value >> 24u) & 0xffu;
3183 bytes[1] = (value >> 16u) & 0xffu;
3184 bytes[2] = (value >> 8u) & 0xffu;
3185 bytes[3] = value & 0xffu;
3186}
3187
3188int pid()
3189{
3190#if defined(CONF_FAMILY_WINDOWS)
3191 return _getpid();
3192#else
3193 return getpid();
3194#endif
3195}
3196
3197void cmdline_fix(int *argc, const char ***argv)
3198{
3199#if defined(CONF_FAMILY_WINDOWS)
3200 int wide_argc = 0;
3201 WCHAR **wide_argv = CommandLineToArgvW(GetCommandLineW(), &wide_argc);
3202 dbg_assert(wide_argv != NULL, "CommandLineToArgvW failure");
3203 dbg_assert(wide_argc > 0, "Invalid argc value");
3204
3205 int total_size = 0;
3206
3207 for(int i = 0; i < wide_argc; i++)
3208 {
3209 int size = WideCharToMultiByte(CP_UTF8, 0, wide_argv[i], -1, nullptr, 0, nullptr, nullptr);
3210 dbg_assert(size != 0, "WideCharToMultiByte failure");
3211 total_size += size;
3212 }
3213
3214 char **new_argv = (char **)malloc((wide_argc + 1) * sizeof(*new_argv));
3215 new_argv[0] = (char *)malloc(total_size);
3216 mem_zero(new_argv[0], total_size);
3217
3218 int remaining_size = total_size;
3219 for(int i = 0; i < wide_argc; i++)
3220 {
3221 int size = WideCharToMultiByte(CP_UTF8, 0, wide_argv[i], -1, new_argv[i], remaining_size, nullptr, nullptr);
3222 dbg_assert(size != 0, "WideCharToMultiByte failure");
3223
3224 remaining_size -= size;
3225 new_argv[i + 1] = new_argv[i] + size;
3226 }
3227
3228 LocalFree(wide_argv);
3229 new_argv[wide_argc] = nullptr;
3230 *argc = wide_argc;
3231 *argv = (const char **)new_argv;
3232#endif
3233}
3234
3235void cmdline_free(int argc, const char **argv)
3236{
3237#if defined(CONF_FAMILY_WINDOWS)
3238 free((void *)*argv);
3239 free((char **)argv);
3240#endif
3241}
3242
3243#if !defined(CONF_PLATFORM_ANDROID)
3244PROCESS shell_execute(const char *file, EShellExecuteWindowState window_state, const char **arguments, const size_t num_arguments)
3245{
3246 dbg_assert((arguments == nullptr) == (num_arguments == 0), "Invalid number of arguments");
3247#if defined(CONF_FAMILY_WINDOWS)
3248 dbg_assert(str_endswith_nocase(file, ".bat") == nullptr && str_endswith_nocase(file, ".cmd") == nullptr, "Running batch files not allowed");
3249 dbg_assert(str_endswith(file, ".exe") != nullptr || num_arguments == 0, "Arguments only allowed with .exe files");
3250
3251 const std::wstring wide_file = windows_utf8_to_wide(file);
3252 std::wstring wide_arguments = windows_args_to_wide(arguments, num_arguments);
3253
3254 SHELLEXECUTEINFOW info;
3255 mem_zero(&info, sizeof(SHELLEXECUTEINFOW));
3256 info.cbSize = sizeof(SHELLEXECUTEINFOW);
3257 info.lpVerb = L"open";
3258 info.lpFile = wide_file.c_str();
3259 info.lpParameters = num_arguments > 0 ? wide_arguments.c_str() : nullptr;
3260 switch(window_state)
3261 {
3262 case EShellExecuteWindowState::FOREGROUND:
3263 info.nShow = SW_SHOW;
3264 break;
3265 case EShellExecuteWindowState::BACKGROUND:
3266 info.nShow = SW_SHOWMINNOACTIVE;
3267 break;
3268 default:
3269 dbg_assert_failed("Invalid window_state: %d", static_cast<int>(window_state));
3270 }
3271 info.fMask = SEE_MASK_NOCLOSEPROCESS;
3272 // Save and restore the FPU control word because ShellExecute might change it
3273 fenv_t floating_point_environment;
3274 int fegetenv_result = fegetenv(&floating_point_environment);
3275 ShellExecuteExW(&info);
3276 if(fegetenv_result == 0)
3277 fesetenv(&floating_point_environment);
3278 return info.hProcess;
3279#elif defined(CONF_FAMILY_UNIX)
3280 char **argv = (char **)malloc(size: (num_arguments + 2) * sizeof(*argv));
3281 pid_t pid;
3282 argv[0] = (char *)file;
3283 for(size_t i = 0; i < num_arguments; ++i)
3284 {
3285 argv[i + 1] = (char *)arguments[i];
3286 }
3287 argv[num_arguments + 1] = NULL;
3288 pid = fork();
3289 if(pid == -1)
3290 {
3291 free(ptr: argv);
3292 return 0;
3293 }
3294 if(pid == 0)
3295 {
3296 execvp(file: file, argv: argv);
3297 _exit(status: 1);
3298 }
3299 free(ptr: argv);
3300 return pid;
3301#endif
3302}
3303
3304int kill_process(PROCESS process)
3305{
3306#if defined(CONF_FAMILY_WINDOWS)
3307 BOOL success = TerminateProcess(process, 0);
3308 BOOL is_alive = is_process_alive(process);
3309 if(success || !is_alive)
3310 {
3311 CloseHandle(process);
3312 return true;
3313 }
3314 return false;
3315#elif defined(CONF_FAMILY_UNIX)
3316 if(!is_process_alive(process))
3317 return true;
3318 int status;
3319 kill(pid: process, SIGTERM);
3320 return waitpid(pid: process, stat_loc: &status, options: 0) != -1;
3321#endif
3322}
3323
3324bool is_process_alive(PROCESS process)
3325{
3326 if(process == INVALID_PROCESS)
3327 return false;
3328#if defined(CONF_FAMILY_WINDOWS)
3329 DWORD exit_code;
3330 GetExitCodeProcess(process, &exit_code);
3331 return exit_code == STILL_ACTIVE;
3332#else
3333 return waitpid(pid: process, stat_loc: nullptr, WNOHANG) == 0;
3334#endif
3335}
3336
3337int open_link(const char *link)
3338{
3339#if defined(CONF_FAMILY_WINDOWS)
3340 const std::wstring wide_link = windows_utf8_to_wide(link);
3341
3342 SHELLEXECUTEINFOW info;
3343 mem_zero(&info, sizeof(SHELLEXECUTEINFOW));
3344 info.cbSize = sizeof(SHELLEXECUTEINFOW);
3345 info.lpVerb = nullptr; // NULL to use the default verb, as "open" may not be available
3346 info.lpFile = wide_link.c_str();
3347 info.nShow = SW_SHOWNORMAL;
3348 // The SEE_MASK_NOASYNC flag ensures that the ShellExecuteEx function
3349 // finishes its DDE conversation before it returns, so it's not necessary
3350 // to pump messages in the calling thread.
3351 // The SEE_MASK_FLAG_NO_UI flag suppresses error messages that would pop up
3352 // when the link cannot be opened, e.g. when a folder does not exist.
3353 // The SEE_MASK_ASYNCOK flag is not used. It would allow the call to
3354 // ShellExecuteEx to return earlier, but it also prevents us from doing
3355 // our own error handling, as the function would always return TRUE.
3356 info.fMask = SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI;
3357 // Save and restore the FPU control word because ShellExecute might change it
3358 fenv_t floating_point_environment;
3359 int fegetenv_result = fegetenv(&floating_point_environment);
3360 BOOL success = ShellExecuteExW(&info);
3361 if(fegetenv_result == 0)
3362 fesetenv(&floating_point_environment);
3363 return success;
3364#elif defined(CONF_PLATFORM_LINUX)
3365 const int pid = fork();
3366 if(pid == 0)
3367 execlp(file: "xdg-open", arg: "xdg-open", link, nullptr);
3368 return pid > 0;
3369#elif defined(CONF_FAMILY_UNIX)
3370 const int pid = fork();
3371 if(pid == 0)
3372 execlp("open", "open", link, nullptr);
3373 return pid > 0;
3374#endif
3375}
3376
3377int open_file(const char *path)
3378{
3379#if defined(CONF_PLATFORM_MACOS)
3380 return open_link(path);
3381#else
3382 // Create a file link so the path can contain forward and
3383 // backward slashes. But the file link must be absolute.
3384 char buf[512];
3385 char workingDir[IO_MAX_PATH_LENGTH];
3386 if(fs_is_relative_path(path))
3387 {
3388 if(!fs_getcwd(buffer: workingDir, buffer_size: sizeof(workingDir)))
3389 return 0;
3390 str_append(dst&: workingDir, src: "/");
3391 }
3392 else
3393 workingDir[0] = '\0';
3394 str_format(buffer: buf, buffer_size: sizeof(buf), format: "file://%s%s", workingDir, path);
3395 return open_link(link: buf);
3396#endif
3397}
3398#endif // !defined(CONF_PLATFORM_ANDROID)
3399
3400struct SECURE_RANDOM_DATA
3401{
3402 std::once_flag initialized_once_flag;
3403#if defined(CONF_FAMILY_WINDOWS)
3404 HCRYPTPROV provider;
3405#else
3406 IOHANDLE urandom;
3407#endif
3408};
3409
3410static struct SECURE_RANDOM_DATA secure_random_data = {};
3411
3412static void ensure_secure_random_init()
3413{
3414 std::call_once(once&: secure_random_data.initialized_once_flag, f: []() {
3415#if defined(CONF_FAMILY_WINDOWS)
3416 if(!CryptAcquireContext(&secure_random_data.provider, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
3417 {
3418 const DWORD LastError = GetLastError();
3419 dbg_assert_failed("Failed to initialize secure random: CryptAcquireContext failure (%ld '%s')", LastError, windows_format_system_message(LastError).c_str());
3420 }
3421#else
3422 secure_random_data.urandom = io_open(filename: "/dev/urandom", flags: IOFLAG_READ);
3423 dbg_assert(secure_random_data.urandom != nullptr, "Failed to initialize secure random: failed to open /dev/urandom");
3424#endif
3425 });
3426}
3427
3428void generate_password(char *buffer, unsigned length, const unsigned short *random, unsigned random_length)
3429{
3430 static const char VALUES[] = "ABCDEFGHKLMNPRSTUVWXYZabcdefghjkmnopqt23456789";
3431 static const size_t NUM_VALUES = sizeof(VALUES) - 1; // Disregard the '\0'.
3432 unsigned i;
3433 dbg_assert(length >= random_length * 2 + 1, "too small buffer");
3434 dbg_assert(NUM_VALUES * NUM_VALUES >= 2048, "need at least 2048 possibilities for 2-character sequences");
3435
3436 buffer[random_length * 2] = 0;
3437
3438 for(i = 0; i < random_length; i++)
3439 {
3440 unsigned short random_number = random[i] % 2048;
3441 buffer[2 * i + 0] = VALUES[random_number / NUM_VALUES];
3442 buffer[2 * i + 1] = VALUES[random_number % NUM_VALUES];
3443 }
3444}
3445
3446#define MAX_PASSWORD_LENGTH 128
3447
3448void secure_random_password(char *buffer, unsigned length, unsigned pw_length)
3449{
3450 unsigned short random[MAX_PASSWORD_LENGTH / 2];
3451 // With 6 characters, we get a password entropy of log(2048) * 6/2 = 33bit.
3452 dbg_assert(length >= pw_length + 1, "too small buffer");
3453 dbg_assert(pw_length >= 6, "too small password length");
3454 dbg_assert(pw_length % 2 == 0, "need an even password length");
3455 dbg_assert(pw_length <= MAX_PASSWORD_LENGTH, "too large password length");
3456
3457 secure_random_fill(bytes: random, length: pw_length);
3458
3459 generate_password(buffer, length, random, random_length: pw_length / 2);
3460}
3461
3462#undef MAX_PASSWORD_LENGTH
3463
3464void secure_random_fill(void *bytes, unsigned length)
3465{
3466 ensure_secure_random_init();
3467#if defined(CONF_FAMILY_WINDOWS)
3468 if(!CryptGenRandom(secure_random_data.provider, length, (unsigned char *)bytes))
3469 {
3470 const DWORD LastError = GetLastError();
3471 dbg_assert_failed("CryptGenRandom failure (%ld '%s')", LastError, windows_format_system_message(LastError).c_str());
3472 }
3473#else
3474 dbg_assert(length == io_read(secure_random_data.urandom, bytes, length), "io_read returned with a short read");
3475#endif
3476}
3477
3478int secure_rand()
3479{
3480 unsigned int i;
3481 secure_random_fill(bytes: &i, length: sizeof(i));
3482 return (int)(i % RAND_MAX);
3483}
3484
3485// From https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2.
3486static unsigned int find_next_power_of_two_minus_one(unsigned int n)
3487{
3488 n--;
3489 n |= n >> 1;
3490 n |= n >> 2;
3491 n |= n >> 4;
3492 n |= n >> 4;
3493 n |= n >> 16;
3494 return n;
3495}
3496
3497int secure_rand_below(int below)
3498{
3499 unsigned int mask = find_next_power_of_two_minus_one(n: below);
3500 dbg_assert(below > 0, "below must be positive");
3501 while(true)
3502 {
3503 unsigned int n;
3504 secure_random_fill(bytes: &n, length: sizeof(n));
3505 n &= mask;
3506 if((int)n < below)
3507 {
3508 return n;
3509 }
3510 }
3511}
3512
3513bool os_version_str(char *version, size_t length)
3514{
3515#if defined(CONF_FAMILY_WINDOWS)
3516 const WCHAR *module_path = L"kernel32.dll";
3517 DWORD handle;
3518 DWORD size = GetFileVersionInfoSizeW(module_path, &handle);
3519 if(!size)
3520 {
3521 return false;
3522 }
3523 void *data = malloc(size);
3524 if(!GetFileVersionInfoW(module_path, handle, size, data))
3525 {
3526 free(data);
3527 return false;
3528 }
3529 VS_FIXEDFILEINFO *fileinfo;
3530 UINT unused;
3531 if(!VerQueryValueW(data, L"\\", (void **)&fileinfo, &unused))
3532 {
3533 free(data);
3534 return false;
3535 }
3536 str_format(version, length, "Windows %hu.%hu.%hu.%hu",
3537 HIWORD(fileinfo->dwProductVersionMS),
3538 LOWORD(fileinfo->dwProductVersionMS),
3539 HIWORD(fileinfo->dwProductVersionLS),
3540 LOWORD(fileinfo->dwProductVersionLS));
3541 free(data);
3542 return true;
3543#else
3544 struct utsname u;
3545 if(uname(name: &u))
3546 {
3547 return false;
3548 }
3549 char extra[128];
3550 extra[0] = 0;
3551
3552 do
3553 {
3554 IOHANDLE os_release = io_open(filename: "/etc/os-release", flags: IOFLAG_READ);
3555 char buf[4096];
3556 int read;
3557 int offset;
3558 char *newline;
3559 if(!os_release)
3560 {
3561 break;
3562 }
3563 read = io_read(io: os_release, buffer: buf, size: sizeof(buf) - 1);
3564 io_close(io: os_release);
3565 buf[read] = 0;
3566 if(str_startswith(str: buf, prefix: "PRETTY_NAME="))
3567 {
3568 offset = 0;
3569 }
3570 else
3571 {
3572 const char *found = str_find(haystack: buf, needle: "\nPRETTY_NAME=");
3573 if(!found)
3574 {
3575 break;
3576 }
3577 offset = found - buf + 1;
3578 }
3579 newline = (char *)str_find(haystack: buf + offset, needle: "\n");
3580 if(newline)
3581 {
3582 *newline = 0;
3583 }
3584 str_format(buffer: extra, buffer_size: sizeof(extra), format: "; %s", buf + offset + 12);
3585 } while(false);
3586
3587 str_format(buffer: version, buffer_size: length, format: "%s %s (%s, %s)%s", u.sysname, u.release, u.machine, u.version, extra);
3588 return true;
3589#endif
3590}
3591
3592void os_locale_str(char *locale, size_t length)
3593{
3594#if defined(CONF_FAMILY_WINDOWS)
3595 wchar_t wide_buffer[LOCALE_NAME_MAX_LENGTH];
3596 dbg_assert(GetUserDefaultLocaleName(wide_buffer, std::size(wide_buffer)) > 0, "GetUserDefaultLocaleName failure");
3597
3598 const std::optional<std::string> buffer = windows_wide_to_utf8(wide_buffer);
3599 dbg_assert(buffer.has_value(), "GetUserDefaultLocaleName returned invalid UTF-16");
3600 str_copy(locale, buffer.value().c_str(), length);
3601#elif defined(CONF_PLATFORM_MACOS)
3602 CFLocaleRef locale_ref = CFLocaleCopyCurrent();
3603 CFStringRef locale_identifier_ref = static_cast<CFStringRef>(CFLocaleGetValue(locale_ref, kCFLocaleIdentifier));
3604
3605 // Count number of UTF16 codepoints, +1 for zero-termination.
3606 // Assume maximum possible length for encoding as UTF-8.
3607 CFIndex locale_identifier_size = (UTF8_BYTE_LENGTH * CFStringGetLength(locale_identifier_ref) + 1) * sizeof(char);
3608 char *locale_identifier = (char *)malloc(locale_identifier_size);
3609 dbg_assert(CFStringGetCString(locale_identifier_ref, locale_identifier, locale_identifier_size, kCFStringEncodingUTF8), "CFStringGetCString failure");
3610
3611 str_copy(locale, locale_identifier, length);
3612
3613 free(locale_identifier);
3614 CFRelease(locale_ref);
3615#else
3616 static const char *ENV_VARIABLES[] = {
3617 "LC_ALL",
3618 "LC_MESSAGES",
3619 "LANG",
3620 };
3621
3622 locale[0] = '\0';
3623 for(const char *env_variable : ENV_VARIABLES)
3624 {
3625 const char *env_value = getenv(name: env_variable);
3626 if(env_value)
3627 {
3628 str_copy(dst: locale, src: env_value, dst_size: length);
3629 break;
3630 }
3631 }
3632#endif
3633
3634 // Ensure RFC 3066 format:
3635 // - use hyphens instead of underscores
3636 // - truncate locale string after first non-standard letter
3637 for(int i = 0; i < str_length(str: locale); ++i)
3638 {
3639 if(locale[i] == '_')
3640 {
3641 locale[i] = '-';
3642 }
3643 else if(locale[i] != '-' && !(locale[i] >= 'a' && locale[i] <= 'z') && !(locale[i] >= 'A' && locale[i] <= 'Z') && !(str_isnum(c: locale[i])))
3644 {
3645 locale[i] = '\0';
3646 break;
3647 }
3648 }
3649
3650 // Use default if we could not determine the locale,
3651 // i.e. if only the C or POSIX locale is available.
3652 if(locale[0] == '\0' || str_comp(a: locale, b: "C") == 0 || str_comp(a: locale, b: "POSIX") == 0)
3653 str_copy(dst: locale, src: "en-US", dst_size: length);
3654}
3655
3656std::chrono::nanoseconds time_get_nanoseconds()
3657{
3658 return std::chrono::nanoseconds(time_get_impl());
3659}
3660