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