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 <atomic>
4#include <cctype>
5#include <charconv>
6#include <chrono>
7#include <cinttypes>
8#include <cmath>
9#include <cstdarg>
10#include <cstdio>
11#include <cstring>
12#include <iomanip> // std::get_time
13#include <iterator> // std::size
14#include <sstream> // std::istringstream
15#include <string_view>
16
17#include "lock.h"
18#include "logger.h"
19#include "system.h"
20
21#include <sys/types.h>
22
23#if defined(CONF_WEBSOCKETS)
24#include <engine/shared/websockets.h>
25#endif
26
27#if defined(CONF_FAMILY_UNIX)
28#include <csignal>
29#include <locale>
30#include <sys/stat.h>
31#include <sys/time.h>
32#include <sys/utsname.h>
33#include <sys/wait.h>
34#include <unistd.h>
35
36/* unix net includes */
37#include <arpa/inet.h>
38#include <cerrno>
39#include <netdb.h>
40#include <netinet/in.h>
41#include <pthread.h>
42#include <sys/ioctl.h>
43#include <sys/socket.h>
44
45#include <dirent.h>
46
47#if defined(CONF_PLATFORM_MACOS)
48// some lock and pthread functions are already defined in headers
49// included from Carbon.h
50// this prevents having duplicate definitions of those
51#define _lock_set_user_
52#define _task_user_
53
54#include <Carbon/Carbon.h>
55#include <CoreFoundation/CoreFoundation.h>
56#include <mach-o/dyld.h>
57#include <mach/mach_time.h>
58
59#if defined(__MAC_10_10) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_10
60#include <pthread/qos.h>
61#endif
62#endif
63
64#elif defined(CONF_FAMILY_WINDOWS)
65#include <windows.h>
66#include <winsock2.h>
67#include <ws2tcpip.h>
68
69#include <cerrno>
70#include <cfenv>
71#include <io.h>
72#include <objbase.h>
73#include <process.h>
74#include <share.h>
75#include <shellapi.h>
76#include <shlobj.h> // SHChangeNotify
77#include <shlwapi.h>
78#include <wincrypt.h>
79#else
80#error NOT IMPLEMENTED
81#endif
82
83#if defined(CONF_PLATFORM_SOLARIS)
84#include <sys/filio.h>
85#endif
86
87IOHANDLE io_stdin()
88{
89 return stdin;
90}
91
92IOHANDLE io_stdout()
93{
94 return stdout;
95}
96
97IOHANDLE io_stderr()
98{
99 return stderr;
100}
101
102IOHANDLE io_current_exe()
103{
104 // From https://stackoverflow.com/a/1024937.
105#if defined(CONF_FAMILY_WINDOWS)
106 wchar_t wide_path[IO_MAX_PATH_LENGTH];
107 if(GetModuleFileNameW(NULL, wide_path, std::size(wide_path)) == 0 || GetLastError() != ERROR_SUCCESS)
108 {
109 return 0;
110 }
111 const std::optional<std::string> path = windows_wide_to_utf8(wide_path);
112 return path.has_value() ? io_open(path.value().c_str(), IOFLAG_READ) : 0;
113#elif defined(CONF_PLATFORM_MACOS)
114 char path[IO_MAX_PATH_LENGTH];
115 uint32_t path_size = sizeof(path);
116 if(_NSGetExecutablePath(path, &path_size))
117 {
118 return 0;
119 }
120 return io_open(path, IOFLAG_READ);
121#else
122 static const char *NAMES[] = {
123 "/proc/self/exe", // Linux, Android
124 "/proc/curproc/exe", // NetBSD
125 "/proc/curproc/file", // DragonFly
126 };
127 for(auto &name : NAMES)
128 {
129 IOHANDLE result = io_open(filename: name, flags: IOFLAG_READ);
130 if(result)
131 {
132 return result;
133 }
134 }
135 return 0;
136#endif
137}
138
139static NETSTATS network_stats = {.sent_packets: 0};
140
141#define VLEN 128
142#define PACKETSIZE 1400
143typedef struct
144{
145#ifdef CONF_PLATFORM_LINUX
146 int pos;
147 int size;
148 struct mmsghdr msgs[VLEN];
149 struct iovec iovecs[VLEN];
150 char bufs[VLEN][PACKETSIZE];
151 char sockaddrs[VLEN][128];
152#else
153 char buf[PACKETSIZE];
154#endif
155} NETSOCKET_BUFFER;
156
157void net_buffer_init(NETSOCKET_BUFFER *buffer);
158void net_buffer_reinit(NETSOCKET_BUFFER *buffer);
159void net_buffer_simple(NETSOCKET_BUFFER *buffer, char **buf, int *size);
160
161struct NETSOCKET_INTERNAL
162{
163 int type;
164 int ipv4sock;
165 int ipv6sock;
166 int web_ipv4sock;
167
168 NETSOCKET_BUFFER buffer;
169};
170static NETSOCKET_INTERNAL invalid_socket = {.type: NETTYPE_INVALID, .ipv4sock: -1, .ipv6sock: -1, .web_ipv4sock: -1};
171
172#define AF_WEBSOCKET_INET (0xee)
173
174std::atomic_bool dbg_assert_failing = false;
175DBG_ASSERT_HANDLER dbg_assert_handler;
176
177bool dbg_assert_has_failed()
178{
179 return dbg_assert_failing.load(m: std::memory_order_acquire);
180}
181
182void dbg_assert_imp(const char *filename, int line, bool test, const char *msg)
183{
184 if(!test)
185 {
186 const bool already_failing = dbg_assert_has_failed();
187 dbg_assert_failing.store(i: true, m: std::memory_order_release);
188 char error[512];
189 str_format(buffer: error, buffer_size: sizeof(error), format: "%s(%d): %s", filename, line, msg);
190 dbg_msg(sys: "assert", fmt: "%s", error);
191 if(!already_failing)
192 {
193 DBG_ASSERT_HANDLER handler = dbg_assert_handler;
194 if(handler)
195 handler(error);
196 }
197 log_global_logger_finish();
198 dbg_break();
199 }
200}
201
202void dbg_break()
203{
204#ifdef __GNUC__
205 __builtin_trap();
206#else
207 abort();
208#endif
209}
210
211void dbg_assert_set_handler(DBG_ASSERT_HANDLER handler)
212{
213 dbg_assert_handler = std::move(handler);
214}
215
216void dbg_msg(const char *sys, const char *fmt, ...)
217{
218 va_list args;
219 va_start(args, fmt);
220 log_log_v(level: LEVEL_INFO, sys, fmt, args);
221 va_end(args);
222}
223
224/* */
225
226void mem_copy(void *dest, const void *source, size_t size)
227{
228 memcpy(dest: dest, src: source, n: size);
229}
230
231void mem_move(void *dest, const void *source, size_t size)
232{
233 memmove(dest: dest, src: source, n: size);
234}
235
236int mem_comp(const void *a, const void *b, size_t size)
237{
238 return memcmp(s1: a, s2: b, n: size);
239}
240
241bool mem_has_null(const void *block, size_t size)
242{
243 const unsigned char *bytes = (const unsigned char *)block;
244 for(size_t i = 0; i < size; i++)
245 {
246 if(bytes[i] == 0)
247 {
248 return true;
249 }
250 }
251 return false;
252}
253
254IOHANDLE io_open(const char *filename, int flags)
255{
256 dbg_assert(flags == IOFLAG_READ || flags == IOFLAG_WRITE || flags == IOFLAG_APPEND, "flags must be read, write or append");
257#if defined(CONF_FAMILY_WINDOWS)
258 const std::wstring wide_filename = windows_utf8_to_wide(filename);
259 DWORD desired_access;
260 DWORD creation_disposition;
261 const char *open_mode;
262 if((flags & IOFLAG_READ) != 0)
263 {
264 desired_access = FILE_READ_DATA;
265 creation_disposition = OPEN_EXISTING;
266 open_mode = "rb";
267 }
268 else if(flags == IOFLAG_WRITE)
269 {
270 desired_access = FILE_WRITE_DATA;
271 creation_disposition = CREATE_ALWAYS;
272 open_mode = "wb";
273 }
274 else if(flags == IOFLAG_APPEND)
275 {
276 desired_access = FILE_APPEND_DATA;
277 creation_disposition = OPEN_ALWAYS;
278 open_mode = "ab";
279 }
280 else
281 {
282 dbg_assert(false, "logic error");
283 return nullptr;
284 }
285 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);
286 if(handle == INVALID_HANDLE_VALUE)
287 return nullptr;
288 const int file_descriptor = _open_osfhandle((intptr_t)handle, 0);
289 dbg_assert(file_descriptor != -1, "_open_osfhandle failure");
290 FILE *file_stream = _fdopen(file_descriptor, open_mode);
291 dbg_assert(file_stream != nullptr, "_fdopen failure");
292 return file_stream;
293#else
294 const char *open_mode;
295 if((flags & IOFLAG_READ) != 0)
296 {
297 open_mode = "rb";
298 }
299 else if(flags == IOFLAG_WRITE)
300 {
301 open_mode = "wb";
302 }
303 else if(flags == IOFLAG_APPEND)
304 {
305 open_mode = "ab";
306 }
307 else
308 {
309 dbg_assert(false, "logic error");
310 return nullptr;
311 }
312 return fopen(filename: filename, modes: open_mode);
313#endif
314}
315
316unsigned io_read(IOHANDLE io, void *buffer, unsigned size)
317{
318 return fread(ptr: buffer, size: 1, n: size, stream: (FILE *)io);
319}
320
321void io_read_all(IOHANDLE io, void **result, unsigned *result_len)
322{
323 long signed_len = io_length(io);
324 unsigned len = signed_len < 0 ? 1024 : (unsigned)signed_len; // use default initial size if we couldn't get the length
325 char *buffer = (char *)malloc(size: len + 1);
326 unsigned read = io_read(io, buffer, size: len + 1); // +1 to check if the file size is larger than expected
327 if(read < len)
328 {
329 buffer = (char *)realloc(ptr: buffer, size: read + 1);
330 len = read;
331 }
332 else if(read > len)
333 {
334 unsigned cap = 2 * read;
335 len = read;
336 buffer = (char *)realloc(ptr: buffer, size: cap);
337 while((read = io_read(io, buffer: buffer + len, size: cap - len)) != 0)
338 {
339 len += read;
340 if(len == cap)
341 {
342 cap *= 2;
343 buffer = (char *)realloc(ptr: buffer, size: cap);
344 }
345 }
346 buffer = (char *)realloc(ptr: buffer, size: len + 1);
347 }
348 buffer[len] = 0;
349 *result = buffer;
350 *result_len = len;
351}
352
353char *io_read_all_str(IOHANDLE io)
354{
355 void *buffer;
356 unsigned len;
357 io_read_all(io, result: &buffer, result_len: &len);
358 if(mem_has_null(block: buffer, size: len))
359 {
360 free(ptr: buffer);
361 return nullptr;
362 }
363 return (char *)buffer;
364}
365
366int io_skip(IOHANDLE io, int size)
367{
368 return io_seek(io, offset: size, origin: IOSEEK_CUR);
369}
370
371int io_seek(IOHANDLE io, int offset, int origin)
372{
373 int real_origin;
374 switch(origin)
375 {
376 case IOSEEK_START:
377 real_origin = SEEK_SET;
378 break;
379 case IOSEEK_CUR:
380 real_origin = SEEK_CUR;
381 break;
382 case IOSEEK_END:
383 real_origin = SEEK_END;
384 break;
385 default:
386 dbg_assert(false, "origin invalid");
387 return -1;
388 }
389 return fseek(stream: (FILE *)io, off: offset, whence: real_origin);
390}
391
392long int io_tell(IOHANDLE io)
393{
394 return ftell(stream: (FILE *)io);
395}
396
397long int io_length(IOHANDLE io)
398{
399 long int length;
400 io_seek(io, offset: 0, origin: IOSEEK_END);
401 length = io_tell(io);
402 io_seek(io, offset: 0, origin: IOSEEK_START);
403 return length;
404}
405
406int io_error(IOHANDLE io)
407{
408 return ferror(stream: (FILE *)io);
409}
410
411unsigned io_write(IOHANDLE io, const void *buffer, unsigned size)
412{
413 return fwrite(ptr: buffer, size: 1, n: size, s: (FILE *)io);
414}
415
416bool io_write_newline(IOHANDLE io)
417{
418#if defined(CONF_FAMILY_WINDOWS)
419 return io_write(io, "\r\n", 2) == 2;
420#else
421 return io_write(io, buffer: "\n", size: 1) == 1;
422#endif
423}
424
425int io_close(IOHANDLE io)
426{
427 return fclose(stream: (FILE *)io) != 0;
428}
429
430int io_flush(IOHANDLE io)
431{
432 return fflush(stream: (FILE *)io);
433}
434
435int io_sync(IOHANDLE io)
436{
437 if(io_flush(io))
438 {
439 return 1;
440 }
441#if defined(CONF_FAMILY_WINDOWS)
442 return FlushFileBuffers((HANDLE)_get_osfhandle(_fileno((FILE *)io))) == FALSE;
443#else
444 return fsync(fd: fileno(stream: (FILE *)io)) != 0;
445#endif
446}
447
448#define ASYNC_BUFSIZE (8 * 1024)
449#define ASYNC_LOCAL_BUFSIZE (64 * 1024)
450
451struct ASYNCIO
452{
453 CLock lock;
454 IOHANDLE io;
455 SEMAPHORE sphore;
456 void *thread;
457
458 unsigned char *buffer;
459 unsigned int buffer_size;
460 unsigned int read_pos;
461 unsigned int write_pos;
462
463 int error;
464 unsigned char finish;
465 unsigned char refcount;
466};
467
468enum
469{
470 ASYNCIO_RUNNING,
471 ASYNCIO_CLOSE,
472 ASYNCIO_EXIT,
473};
474
475struct BUFFERS
476{
477 unsigned char *buf1;
478 unsigned int len1;
479 unsigned char *buf2;
480 unsigned int len2;
481};
482
483static void buffer_ptrs(ASYNCIO *aio, struct BUFFERS *buffers)
484{
485 mem_zero(block: buffers, size: sizeof(*buffers));
486 if(aio->read_pos < aio->write_pos)
487 {
488 buffers->buf1 = aio->buffer + aio->read_pos;
489 buffers->len1 = aio->write_pos - aio->read_pos;
490 }
491 else if(aio->read_pos > aio->write_pos)
492 {
493 buffers->buf1 = aio->buffer + aio->read_pos;
494 buffers->len1 = aio->buffer_size - aio->read_pos;
495 buffers->buf2 = aio->buffer;
496 buffers->len2 = aio->write_pos;
497 }
498}
499
500static void aio_handle_free_and_unlock(ASYNCIO *aio) RELEASE(aio->lock)
501{
502 int do_free;
503 aio->refcount--;
504
505 do_free = aio->refcount == 0;
506 aio->lock.unlock();
507 if(do_free)
508 {
509 free(ptr: aio->buffer);
510 sphore_destroy(sem: &aio->sphore);
511 delete aio;
512 }
513}
514
515static void aio_thread(void *user)
516{
517 ASYNCIO *aio = (ASYNCIO *)user;
518
519 aio->lock.lock();
520 while(true)
521 {
522 struct BUFFERS buffers;
523 int result_io_error;
524 unsigned char local_buffer[ASYNC_LOCAL_BUFSIZE];
525 unsigned int local_buffer_len = 0;
526
527 if(aio->read_pos == aio->write_pos)
528 {
529 if(aio->finish != ASYNCIO_RUNNING)
530 {
531 if(aio->finish == ASYNCIO_CLOSE)
532 {
533 io_close(io: aio->io);
534 }
535 aio_handle_free_and_unlock(aio);
536 break;
537 }
538 aio->lock.unlock();
539 sphore_wait(sem: &aio->sphore);
540 aio->lock.lock();
541 continue;
542 }
543
544 buffer_ptrs(aio, buffers: &buffers);
545 if(buffers.buf1)
546 {
547 if(buffers.len1 > sizeof(local_buffer) - local_buffer_len)
548 {
549 buffers.len1 = sizeof(local_buffer) - local_buffer_len;
550 }
551 mem_copy(dest: local_buffer + local_buffer_len, source: buffers.buf1, size: buffers.len1);
552 local_buffer_len += buffers.len1;
553 if(buffers.buf2)
554 {
555 if(buffers.len2 > sizeof(local_buffer) - local_buffer_len)
556 {
557 buffers.len2 = sizeof(local_buffer) - local_buffer_len;
558 }
559 mem_copy(dest: local_buffer + local_buffer_len, source: buffers.buf2, size: buffers.len2);
560 local_buffer_len += buffers.len2;
561 }
562 }
563 aio->read_pos = (aio->read_pos + buffers.len1 + buffers.len2) % aio->buffer_size;
564 aio->lock.unlock();
565
566 io_write(io: aio->io, buffer: local_buffer, size: local_buffer_len);
567 io_flush(io: aio->io);
568 result_io_error = io_error(io: aio->io);
569
570 aio->lock.lock();
571 aio->error = result_io_error;
572 }
573}
574
575ASYNCIO *aio_new(IOHANDLE io)
576{
577 ASYNCIO *aio = new ASYNCIO;
578 if(!aio)
579 {
580 return 0;
581 }
582 aio->io = io;
583 sphore_init(sem: &aio->sphore);
584 aio->thread = 0;
585
586 aio->buffer = (unsigned char *)malloc(ASYNC_BUFSIZE);
587 if(!aio->buffer)
588 {
589 sphore_destroy(sem: &aio->sphore);
590 delete aio;
591 return 0;
592 }
593 aio->buffer_size = ASYNC_BUFSIZE;
594 aio->read_pos = 0;
595 aio->write_pos = 0;
596 aio->error = 0;
597 aio->finish = ASYNCIO_RUNNING;
598 aio->refcount = 2;
599
600 aio->thread = thread_init(threadfunc: aio_thread, user: aio, name: "aio");
601 if(!aio->thread)
602 {
603 free(ptr: aio->buffer);
604 sphore_destroy(sem: &aio->sphore);
605 delete aio;
606 return 0;
607 }
608 return aio;
609}
610
611static unsigned int buffer_len(ASYNCIO *aio)
612{
613 if(aio->write_pos >= aio->read_pos)
614 {
615 return aio->write_pos - aio->read_pos;
616 }
617 else
618 {
619 return aio->buffer_size + aio->write_pos - aio->read_pos;
620 }
621}
622
623static unsigned int next_buffer_size(unsigned int cur_size, unsigned int need_size)
624{
625 while(cur_size < need_size)
626 {
627 cur_size *= 2;
628 }
629 return cur_size;
630}
631
632void aio_lock(ASYNCIO *aio) ACQUIRE(aio->lock)
633{
634 aio->lock.lock();
635}
636
637void aio_unlock(ASYNCIO *aio) RELEASE(aio->lock)
638{
639 aio->lock.unlock();
640 sphore_signal(sem: &aio->sphore);
641}
642
643void aio_write_unlocked(ASYNCIO *aio, const void *buffer, unsigned size)
644{
645 unsigned int remaining;
646 remaining = aio->buffer_size - buffer_len(aio);
647
648 // Don't allow full queue to distinguish between empty and full queue.
649 if(size < remaining)
650 {
651 unsigned int remaining_contiguous = aio->buffer_size - aio->write_pos;
652 if(size > remaining_contiguous)
653 {
654 mem_copy(dest: aio->buffer + aio->write_pos, source: buffer, size: remaining_contiguous);
655 size -= remaining_contiguous;
656 buffer = ((unsigned char *)buffer) + remaining_contiguous;
657 aio->write_pos = 0;
658 }
659 mem_copy(dest: aio->buffer + aio->write_pos, source: buffer, size);
660 aio->write_pos = (aio->write_pos + size) % aio->buffer_size;
661 }
662 else
663 {
664 // Add 1 so the new buffer isn't completely filled.
665 unsigned int new_written = buffer_len(aio) + size + 1;
666 unsigned int next_size = next_buffer_size(cur_size: aio->buffer_size, need_size: new_written);
667 unsigned int next_len = 0;
668 unsigned char *next_buffer = (unsigned char *)malloc(size: next_size);
669
670 struct BUFFERS buffers;
671 buffer_ptrs(aio, buffers: &buffers);
672 if(buffers.buf1)
673 {
674 mem_copy(dest: next_buffer + next_len, source: buffers.buf1, size: buffers.len1);
675 next_len += buffers.len1;
676 if(buffers.buf2)
677 {
678 mem_copy(dest: next_buffer + next_len, source: buffers.buf2, size: buffers.len2);
679 next_len += buffers.len2;
680 }
681 }
682 mem_copy(dest: next_buffer + next_len, source: buffer, size);
683 next_len += size;
684
685 free(ptr: aio->buffer);
686 aio->buffer = next_buffer;
687 aio->buffer_size = next_size;
688 aio->read_pos = 0;
689 aio->write_pos = next_len;
690 }
691}
692
693void aio_write(ASYNCIO *aio, const void *buffer, unsigned size)
694{
695 aio_lock(aio);
696 aio_write_unlocked(aio, buffer, size);
697 aio_unlock(aio);
698}
699
700void aio_write_newline_unlocked(ASYNCIO *aio)
701{
702#if defined(CONF_FAMILY_WINDOWS)
703 aio_write_unlocked(aio, "\r\n", 2);
704#else
705 aio_write_unlocked(aio, buffer: "\n", size: 1);
706#endif
707}
708
709void aio_write_newline(ASYNCIO *aio)
710{
711 aio_lock(aio);
712 aio_write_newline_unlocked(aio);
713 aio_unlock(aio);
714}
715
716int aio_error(ASYNCIO *aio)
717{
718 CLockScope ls(aio->lock);
719 return aio->error;
720}
721
722void aio_free(ASYNCIO *aio)
723{
724 aio->lock.lock();
725 if(aio->thread)
726 {
727 thread_detach(thread: aio->thread);
728 aio->thread = 0;
729 }
730 aio_handle_free_and_unlock(aio);
731}
732
733void aio_close(ASYNCIO *aio)
734{
735 {
736 CLockScope ls(aio->lock);
737 aio->finish = ASYNCIO_CLOSE;
738 }
739 sphore_signal(sem: &aio->sphore);
740}
741
742void aio_wait(ASYNCIO *aio)
743{
744 void *thread;
745 {
746 CLockScope ls(aio->lock);
747 thread = aio->thread;
748 aio->thread = 0;
749 if(aio->finish == ASYNCIO_RUNNING)
750 {
751 aio->finish = ASYNCIO_EXIT;
752 }
753 }
754 sphore_signal(sem: &aio->sphore);
755 thread_wait(thread);
756}
757
758struct THREAD_RUN
759{
760 void (*threadfunc)(void *);
761 void *u;
762};
763
764#if defined(CONF_FAMILY_UNIX)
765static void *thread_run(void *user)
766#elif defined(CONF_FAMILY_WINDOWS)
767static unsigned long __stdcall thread_run(void *user)
768#else
769#error not implemented
770#endif
771{
772#if defined(CONF_FAMILY_WINDOWS)
773 CWindowsComLifecycle WindowsComLifecycle(false);
774#endif
775 struct THREAD_RUN *data = (THREAD_RUN *)user;
776 void (*threadfunc)(void *) = data->threadfunc;
777 void *u = data->u;
778 free(ptr: data);
779 threadfunc(u);
780 return 0;
781}
782
783void *thread_init(void (*threadfunc)(void *), void *u, const char *name)
784{
785 struct THREAD_RUN *data = (THREAD_RUN *)malloc(size: sizeof(*data));
786 data->threadfunc = threadfunc;
787 data->u = u;
788#if defined(CONF_FAMILY_UNIX)
789 {
790 pthread_attr_t attr;
791 dbg_assert(pthread_attr_init(&attr) == 0, "pthread_attr_init failure");
792#if defined(CONF_PLATFORM_MACOS) && defined(__MAC_10_10) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_10
793 dbg_assert(pthread_attr_set_qos_class_np(&attr, QOS_CLASS_USER_INTERACTIVE, 0) == 0, "pthread_attr_set_qos_class_np failure");
794#endif
795 pthread_t id;
796 dbg_assert(pthread_create(&id, &attr, thread_run, data) == 0, "pthread_create failure");
797 return (void *)id;
798 }
799#elif defined(CONF_FAMILY_WINDOWS)
800 HANDLE thread = CreateThread(nullptr, 0, thread_run, data, 0, nullptr);
801 dbg_assert(thread != nullptr, "CreateThread failure");
802 // TODO: Set thread name using SetThreadDescription (would require minimum Windows 10 version 1607)
803 return thread;
804#else
805#error not implemented
806#endif
807}
808
809void thread_wait(void *thread)
810{
811#if defined(CONF_FAMILY_UNIX)
812 dbg_assert(pthread_join((pthread_t)thread, nullptr) == 0, "pthread_join failure");
813#elif defined(CONF_FAMILY_WINDOWS)
814 dbg_assert(WaitForSingleObject((HANDLE)thread, INFINITE) == WAIT_OBJECT_0, "WaitForSingleObject failure");
815 dbg_assert(CloseHandle(thread), "CloseHandle failure");
816#else
817#error not implemented
818#endif
819}
820
821void thread_yield()
822{
823#if defined(CONF_FAMILY_UNIX)
824 dbg_assert(sched_yield() == 0, "sched_yield failure");
825#elif defined(CONF_FAMILY_WINDOWS)
826 Sleep(0);
827#else
828#error not implemented
829#endif
830}
831
832void thread_detach(void *thread)
833{
834#if defined(CONF_FAMILY_UNIX)
835 dbg_assert(pthread_detach((pthread_t)thread) == 0, "pthread_detach failure");
836#elif defined(CONF_FAMILY_WINDOWS)
837 dbg_assert(CloseHandle(thread), "CloseHandle failure");
838#else
839#error not implemented
840#endif
841}
842
843void thread_init_and_detach(void (*threadfunc)(void *), void *u, const char *name)
844{
845 void *thread = thread_init(threadfunc, u, name);
846 thread_detach(thread);
847}
848
849#if defined(CONF_FAMILY_WINDOWS)
850void sphore_init(SEMAPHORE *sem)
851{
852 *sem = CreateSemaphoreW(nullptr, 0, std::numeric_limits<LONG>::max(), nullptr);
853 dbg_assert(*sem != nullptr, "CreateSemaphoreW failure");
854}
855void sphore_wait(SEMAPHORE *sem)
856{
857 dbg_assert(WaitForSingleObject((HANDLE)*sem, INFINITE) == WAIT_OBJECT_0, "WaitForSingleObject failure");
858}
859void sphore_signal(SEMAPHORE *sem)
860{
861 dbg_assert(ReleaseSemaphore((HANDLE)*sem, 1, nullptr), "ReleaseSemaphore failure");
862}
863void sphore_destroy(SEMAPHORE *sem)
864{
865 dbg_assert(CloseHandle((HANDLE)*sem), "CloseHandle failure");
866}
867#elif defined(CONF_PLATFORM_MACOS)
868void sphore_init(SEMAPHORE *sem)
869{
870 char aBuf[32];
871 str_format(aBuf, sizeof(aBuf), "%p", (void *)sem);
872 *sem = sem_open(aBuf, O_CREAT | O_EXCL, S_IRWXU | S_IRWXG, 0);
873 dbg_assert(*sem != SEM_FAILED, "sem_open failure");
874}
875void sphore_wait(SEMAPHORE *sem)
876{
877 while(true)
878 {
879 if(sem_wait(*sem) == 0)
880 break;
881 dbg_assert(errno == EINTR, "sem_wait failure");
882 }
883}
884void sphore_signal(SEMAPHORE *sem)
885{
886 dbg_assert(sem_post(*sem) == 0, "sem_post failure");
887}
888void sphore_destroy(SEMAPHORE *sem)
889{
890 dbg_assert(sem_close(*sem) == 0, "sem_close failure");
891 char aBuf[32];
892 str_format(aBuf, sizeof(aBuf), "%p", (void *)sem);
893 dbg_assert(sem_unlink(aBuf) == 0, "sem_unlink failure");
894}
895#elif defined(CONF_FAMILY_UNIX)
896void sphore_init(SEMAPHORE *sem)
897{
898 dbg_assert(sem_init(sem, 0, 0) == 0, "sem_init failure");
899}
900void sphore_wait(SEMAPHORE *sem)
901{
902 while(true)
903 {
904 if(sem_wait(sem: sem) == 0)
905 break;
906 dbg_assert(errno == EINTR, "sem_wait failure");
907 }
908}
909void sphore_signal(SEMAPHORE *sem)
910{
911 dbg_assert(sem_post(sem) == 0, "sem_post failure");
912}
913void sphore_destroy(SEMAPHORE *sem)
914{
915 dbg_assert(sem_destroy(sem) == 0, "sem_destroy failure");
916}
917#endif
918
919static int new_tick = -1;
920
921void set_new_tick()
922{
923 new_tick = 1;
924}
925
926/* ----- time ----- */
927static_assert(std::chrono::steady_clock::is_steady, "Compiler does not support steady clocks, it might be out of date.");
928static_assert(std::chrono::steady_clock::period::den / std::chrono::steady_clock::period::num >= 1000000000, "Compiler has a bad timer precision and might be out of date.");
929static const std::chrono::time_point<std::chrono::steady_clock> tw_start_time = std::chrono::steady_clock::now();
930
931int64_t time_get_impl()
932{
933 return std::chrono::duration_cast<std::chrono::nanoseconds>(d: std::chrono::steady_clock::now() - tw_start_time).count();
934}
935
936int64_t time_get()
937{
938 static int64_t last = 0;
939 if(new_tick == 0)
940 return last;
941 if(new_tick != -1)
942 new_tick = 0;
943
944 last = time_get_impl();
945 return last;
946}
947
948int64_t time_freq()
949{
950 using namespace std::chrono_literals;
951 return std::chrono::nanoseconds(1s).count();
952}
953
954/* ----- network ----- */
955
956const 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};
957
958static void netaddr_to_sockaddr_in(const NETADDR *src, struct sockaddr_in *dest)
959{
960 mem_zero(block: dest, size: sizeof(struct sockaddr_in));
961 if(src->type != NETTYPE_IPV4 && src->type != NETTYPE_WEBSOCKET_IPV4)
962 {
963 dbg_msg(sys: "system", fmt: "couldn't convert NETADDR of type %d to ipv4", src->type);
964 return;
965 }
966
967 dest->sin_family = AF_INET;
968 dest->sin_port = htons(hostshort: src->port);
969 mem_copy(dest: &dest->sin_addr.s_addr, source: src->ip, size: 4);
970}
971
972static void netaddr_to_sockaddr_in6(const NETADDR *src, struct sockaddr_in6 *dest)
973{
974 mem_zero(block: dest, size: sizeof(struct sockaddr_in6));
975 if(src->type != NETTYPE_IPV6)
976 {
977 dbg_msg(sys: "system", fmt: "couldn't not convert NETADDR of type %d to ipv6", src->type);
978 return;
979 }
980
981 dest->sin6_family = AF_INET6;
982 dest->sin6_port = htons(hostshort: src->port);
983 mem_copy(dest: &dest->sin6_addr.s6_addr, source: src->ip, size: 16);
984}
985
986static void sockaddr_to_netaddr(const struct sockaddr *src, NETADDR *dst)
987{
988 if(src->sa_family == AF_INET)
989 {
990 mem_zero(block: dst, size: sizeof(NETADDR));
991 dst->type = NETTYPE_IPV4;
992 dst->port = htons(hostshort: ((struct sockaddr_in *)src)->sin_port);
993 mem_copy(dest: dst->ip, source: &((struct sockaddr_in *)src)->sin_addr.s_addr, size: 4);
994 }
995 else if(src->sa_family == AF_WEBSOCKET_INET)
996 {
997 mem_zero(block: dst, size: sizeof(NETADDR));
998 dst->type = NETTYPE_WEBSOCKET_IPV4;
999 dst->port = htons(hostshort: ((struct sockaddr_in *)src)->sin_port);
1000 mem_copy(dest: dst->ip, source: &((struct sockaddr_in *)src)->sin_addr.s_addr, size: 4);
1001 }
1002 else if(src->sa_family == AF_INET6)
1003 {
1004 mem_zero(block: dst, size: sizeof(NETADDR));
1005 dst->type = NETTYPE_IPV6;
1006 dst->port = htons(hostshort: ((struct sockaddr_in6 *)src)->sin6_port);
1007 mem_copy(dest: dst->ip, source: &((struct sockaddr_in6 *)src)->sin6_addr.s6_addr, size: 16);
1008 }
1009 else
1010 {
1011 mem_zero(block: dst, size: sizeof(struct sockaddr));
1012 dbg_msg(sys: "system", fmt: "couldn't convert sockaddr of family %d", src->sa_family);
1013 }
1014}
1015
1016int net_addr_comp(const NETADDR *a, const NETADDR *b)
1017{
1018 return mem_comp(a, b, size: sizeof(NETADDR));
1019}
1020
1021bool NETADDR::operator==(const NETADDR &other) const
1022{
1023 return net_addr_comp(a: this, b: &other) == 0;
1024}
1025
1026int net_addr_comp_noport(const NETADDR *a, const NETADDR *b)
1027{
1028 NETADDR ta = *a, tb = *b;
1029 ta.port = tb.port = 0;
1030
1031 return net_addr_comp(a: &ta, b: &tb);
1032}
1033
1034void net_addr_str_v6(const unsigned short ip[8], int port, char *buffer, int buffer_size)
1035{
1036 int longest_seq_len = 0;
1037 int longest_seq_start = -1;
1038 int w = 0;
1039 int i;
1040 {
1041 int seq_len = 0;
1042 int seq_start = -1;
1043 // Determine longest sequence of zeros.
1044 for(i = 0; i < 8 + 1; i++)
1045 {
1046 if(seq_start != -1)
1047 {
1048 if(i == 8 || ip[i] != 0)
1049 {
1050 if(longest_seq_len < seq_len)
1051 {
1052 longest_seq_len = seq_len;
1053 longest_seq_start = seq_start;
1054 }
1055 seq_len = 0;
1056 seq_start = -1;
1057 }
1058 else
1059 {
1060 seq_len += 1;
1061 }
1062 }
1063 else
1064 {
1065 if(i != 8 && ip[i] == 0)
1066 {
1067 seq_start = i;
1068 seq_len = 1;
1069 }
1070 }
1071 }
1072 }
1073 if(longest_seq_len <= 1)
1074 {
1075 longest_seq_len = 0;
1076 longest_seq_start = -1;
1077 }
1078 w += str_copy(dst: buffer + w, src: "[", dst_size: buffer_size - w);
1079 for(i = 0; i < 8; i++)
1080 {
1081 if(longest_seq_start <= i && i < longest_seq_start + longest_seq_len)
1082 {
1083 if(i == longest_seq_start)
1084 {
1085 w += str_copy(dst: buffer + w, src: "::", dst_size: buffer_size - w);
1086 }
1087 }
1088 else
1089 {
1090 const char *colon = (i == 0 || i == longest_seq_start + longest_seq_len) ? "" : ":";
1091 w += str_format(buffer: buffer + w, buffer_size: buffer_size - w, format: "%s%x", colon, ip[i]);
1092 }
1093 }
1094 w += str_copy(dst: buffer + w, src: "]", dst_size: buffer_size - w);
1095 if(port >= 0)
1096 {
1097 str_format(buffer: buffer + w, buffer_size: buffer_size - w, format: ":%d", port);
1098 }
1099}
1100
1101void net_addr_str(const NETADDR *addr, char *string, int max_length, int add_port)
1102{
1103 if(addr->type == NETTYPE_IPV4 || addr->type == NETTYPE_WEBSOCKET_IPV4)
1104 {
1105 if(add_port != 0)
1106 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);
1107 else
1108 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]);
1109 }
1110 else if(addr->type == NETTYPE_IPV6)
1111 {
1112 int port = -1;
1113 unsigned short ip[8];
1114 int i;
1115 if(add_port)
1116 {
1117 port = addr->port;
1118 }
1119 for(i = 0; i < 8; i++)
1120 {
1121 ip[i] = (addr->ip[i * 2] << 8) | (addr->ip[i * 2 + 1]);
1122 }
1123 net_addr_str_v6(ip, port, buffer: string, buffer_size: max_length);
1124 }
1125 else
1126 str_format(buffer: string, buffer_size: max_length, format: "unknown type %d", addr->type);
1127}
1128
1129static int priv_net_extract(const char *hostname, char *host, int max_host, int *port)
1130{
1131 int i;
1132
1133 *port = 0;
1134 host[0] = 0;
1135
1136 if(hostname[0] == '[')
1137 {
1138 // ipv6 mode
1139 for(i = 1; i < max_host && hostname[i] && hostname[i] != ']'; i++)
1140 host[i - 1] = hostname[i];
1141 host[i - 1] = 0;
1142 if(hostname[i] != ']') // malformatted
1143 return -1;
1144
1145 i++;
1146 if(hostname[i] == ':')
1147 *port = str_toint(str: hostname + i + 1);
1148 }
1149 else
1150 {
1151 // generic mode (ipv4, hostname etc)
1152 for(i = 0; i < max_host - 1 && hostname[i] && hostname[i] != ':'; i++)
1153 host[i] = hostname[i];
1154 host[i] = 0;
1155
1156 if(hostname[i] == ':')
1157 *port = str_toint(str: hostname + i + 1);
1158 }
1159
1160 return 0;
1161}
1162
1163int net_host_lookup_impl(const char *hostname, NETADDR *addr, int types)
1164{
1165 struct addrinfo hints;
1166 struct addrinfo *result = NULL;
1167 int e;
1168 char host[256];
1169 int port = 0;
1170
1171 if(priv_net_extract(hostname, host, max_host: sizeof(host), port: &port))
1172 return -1;
1173
1174 dbg_msg(sys: "host_lookup", fmt: "host='%s' port=%d %d", host, port, types);
1175
1176 mem_zero(block: &hints, size: sizeof(hints));
1177
1178 hints.ai_family = AF_UNSPEC;
1179
1180 if(types == NETTYPE_IPV4)
1181 hints.ai_family = AF_INET;
1182 else if(types == NETTYPE_IPV6)
1183 hints.ai_family = AF_INET6;
1184
1185 e = getaddrinfo(name: host, NULL, req: &hints, pai: &result);
1186
1187 if(!result)
1188 return -1;
1189
1190 if(e != 0)
1191 {
1192 freeaddrinfo(ai: result);
1193 return -1;
1194 }
1195
1196 sockaddr_to_netaddr(src: result->ai_addr, dst: addr);
1197 addr->port = port;
1198 freeaddrinfo(ai: result);
1199 return 0;
1200}
1201
1202int net_host_lookup(const char *hostname, NETADDR *addr, int types)
1203{
1204 const char *ws_hostname = str_startswith(str: hostname, prefix: "ws://");
1205 if(ws_hostname)
1206 {
1207 if((types & NETTYPE_WEBSOCKET_IPV4) == 0)
1208 {
1209 return -1;
1210 }
1211 int result = net_host_lookup_impl(hostname: ws_hostname, addr, types: NETTYPE_IPV4);
1212 if(result == 0 && addr->type == NETTYPE_IPV4)
1213 {
1214 addr->type = NETTYPE_WEBSOCKET_IPV4;
1215 }
1216 return result;
1217 }
1218 return net_host_lookup_impl(hostname, addr, types: types & ~NETTYPE_WEBSOCKET_IPV4);
1219}
1220
1221static int parse_int(int *out, const char **str)
1222{
1223 int i = 0;
1224 *out = 0;
1225 if(!str_isnum(c: **str))
1226 return -1;
1227
1228 i = **str - '0';
1229 (*str)++;
1230
1231 while(true)
1232 {
1233 if(!str_isnum(c: **str))
1234 {
1235 *out = i;
1236 return 0;
1237 }
1238
1239 i = (i * 10) + (**str - '0');
1240 (*str)++;
1241 }
1242
1243 return 0;
1244}
1245
1246static int parse_char(char c, const char **str)
1247{
1248 if(**str != c)
1249 return -1;
1250 (*str)++;
1251 return 0;
1252}
1253
1254static int parse_uint8(unsigned char *out, const char **str)
1255{
1256 int i;
1257 if(parse_int(out: &i, str) != 0)
1258 return -1;
1259 if(i < 0 || i > 0xff)
1260 return -1;
1261 *out = i;
1262 return 0;
1263}
1264
1265static int parse_uint16(unsigned short *out, const char **str)
1266{
1267 int i;
1268 if(parse_int(out: &i, str) != 0)
1269 return -1;
1270 if(i < 0 || i > 0xffff)
1271 return -1;
1272 *out = i;
1273 return 0;
1274}
1275
1276int net_addr_from_url(NETADDR *addr, const char *string, char *host_buf, size_t host_buf_size)
1277{
1278 const char *str = str_startswith(str: string, prefix: "tw-0.6+udp://");
1279 if(!str)
1280 return 1;
1281
1282 mem_zero(block: addr, size: sizeof(*addr));
1283
1284 int length = str_length(str);
1285 int start = 0;
1286 int end = length;
1287 for(int i = 0; i < length; i++)
1288 {
1289 if(str[i] == '@')
1290 {
1291 if(start != 0)
1292 {
1293 // Two at signs.
1294 return true;
1295 }
1296 start = i + 1;
1297 }
1298 else if(str[i] == '/' || str[i] == '?' || str[i] == '#')
1299 {
1300 end = i;
1301 break;
1302 }
1303 }
1304
1305 char host[128];
1306 str_truncate(dst: host, dst_size: sizeof(host), src: str + start, truncation_len: end - start);
1307 if(host_buf)
1308 str_copy(dst: host_buf, src: host, dst_size: host_buf_size);
1309
1310 return net_addr_from_str(addr, string: host);
1311}
1312
1313int net_addr_from_str(NETADDR *addr, const char *string)
1314{
1315 const char *str = string;
1316 mem_zero(block: addr, size: sizeof(NETADDR));
1317
1318 if(str[0] == '[')
1319 {
1320 /* ipv6 */
1321 struct sockaddr_in6 sa6;
1322 char buf[128];
1323 int i;
1324 str++;
1325 for(i = 0; i < 127 && str[i] && str[i] != ']'; i++)
1326 buf[i] = str[i];
1327 buf[i] = 0;
1328 str += i;
1329#if defined(CONF_FAMILY_WINDOWS)
1330 {
1331 int size;
1332 sa6.sin6_family = AF_INET6;
1333 size = (int)sizeof(sa6);
1334 if(WSAStringToAddressA(buf, AF_INET6, NULL, (struct sockaddr *)&sa6, &size) != 0)
1335 return -1;
1336 }
1337#else
1338 sa6.sin6_family = AF_INET6;
1339
1340 if(inet_pton(AF_INET6, cp: buf, buf: &sa6.sin6_addr) != 1)
1341 return -1;
1342#endif
1343 sockaddr_to_netaddr(src: (struct sockaddr *)&sa6, dst: addr);
1344
1345 if(*str == ']')
1346 {
1347 str++;
1348 if(*str == ':')
1349 {
1350 str++;
1351 if(parse_uint16(out: &addr->port, str: &str))
1352 return -1;
1353 }
1354 else
1355 {
1356 addr->port = 0;
1357 }
1358 }
1359 else
1360 return -1;
1361
1362 return 0;
1363 }
1364 else
1365 {
1366 /* ipv4 */
1367 if(parse_uint8(out: &addr->ip[0], str: &str))
1368 return -1;
1369 if(parse_char(c: '.', str: &str))
1370 return -1;
1371 if(parse_uint8(out: &addr->ip[1], str: &str))
1372 return -1;
1373 if(parse_char(c: '.', str: &str))
1374 return -1;
1375 if(parse_uint8(out: &addr->ip[2], str: &str))
1376 return -1;
1377 if(parse_char(c: '.', str: &str))
1378 return -1;
1379 if(parse_uint8(out: &addr->ip[3], str: &str))
1380 return -1;
1381 if(*str == ':')
1382 {
1383 str++;
1384 if(parse_uint16(out: &addr->port, str: &str))
1385 return -1;
1386 }
1387 if(*str != '\0')
1388 return -1;
1389
1390 addr->type = NETTYPE_IPV4;
1391 }
1392
1393 return 0;
1394}
1395
1396static void priv_net_close_socket(int sock)
1397{
1398#if defined(CONF_FAMILY_WINDOWS)
1399 closesocket(sock);
1400#else
1401 if(close(fd: sock) != 0)
1402 dbg_msg(sys: "socket", fmt: "close failed: %d", errno);
1403#endif
1404}
1405
1406static int priv_net_close_all_sockets(NETSOCKET sock)
1407{
1408 /* close down ipv4 */
1409 if(sock->ipv4sock >= 0)
1410 {
1411 priv_net_close_socket(sock: sock->ipv4sock);
1412 sock->ipv4sock = -1;
1413 sock->type &= ~NETTYPE_IPV4;
1414 }
1415
1416#if defined(CONF_WEBSOCKETS)
1417 /* close down websocket_ipv4 */
1418 if(sock->web_ipv4sock >= 0)
1419 {
1420 websocket_destroy(socket: sock->web_ipv4sock);
1421 sock->web_ipv4sock = -1;
1422 sock->type &= ~NETTYPE_WEBSOCKET_IPV4;
1423 }
1424#endif
1425
1426 /* close down ipv6 */
1427 if(sock->ipv6sock >= 0)
1428 {
1429 priv_net_close_socket(sock: sock->ipv6sock);
1430 sock->ipv6sock = -1;
1431 sock->type &= ~NETTYPE_IPV6;
1432 }
1433
1434 free(ptr: sock);
1435 return 0;
1436}
1437
1438#if defined(CONF_FAMILY_WINDOWS)
1439std::string windows_format_system_message(unsigned long error)
1440{
1441 WCHAR *wide_message;
1442 const DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_MAX_WIDTH_MASK;
1443 if(FormatMessageW(flags, NULL, error, 0, (LPWSTR)&wide_message, 0, NULL) == 0)
1444 return "unknown error";
1445
1446 std::optional<std::string> message = windows_wide_to_utf8(wide_message);
1447 LocalFree(wide_message);
1448 return message.value_or("(invalid UTF-16 in error message)");
1449}
1450#endif
1451
1452static int priv_net_create_socket(int domain, int type, struct sockaddr *addr, int sockaddrlen)
1453{
1454 int sock, e;
1455
1456 /* create socket */
1457 sock = socket(domain: domain, type: type, protocol: 0);
1458 if(sock < 0)
1459 {
1460#if defined(CONF_FAMILY_WINDOWS)
1461 int error = WSAGetLastError();
1462 const std::string message = windows_format_system_message(error);
1463 dbg_msg("net", "failed to create socket with domain %d and type %d (%d '%s')", domain, type, error, message.c_str());
1464#else
1465 dbg_msg(sys: "net", fmt: "failed to create socket with domain %d and type %d (%d '%s')", domain, type, errno, strerror(errno));
1466#endif
1467 return -1;
1468 }
1469
1470#if defined(CONF_FAMILY_UNIX)
1471 /* on tcp sockets set SO_REUSEADDR
1472 to fix port rebind on restart */
1473 if(domain == AF_INET && type == SOCK_STREAM)
1474 {
1475 int option = 1;
1476 if(setsockopt(fd: sock, SOL_SOCKET, SO_REUSEADDR, optval: &option, optlen: sizeof(option)) != 0)
1477 dbg_msg(sys: "socket", fmt: "Setting SO_REUSEADDR failed: %d", errno);
1478 }
1479#endif
1480
1481 /* set to IPv6 only if that's what we are creating */
1482#if defined(IPV6_V6ONLY) /* windows sdk 6.1 and higher */
1483 if(domain == AF_INET6)
1484 {
1485 int ipv6only = 1;
1486 if(setsockopt(fd: sock, IPPROTO_IPV6, IPV6_V6ONLY, optval: (const char *)&ipv6only, optlen: sizeof(ipv6only)) != 0)
1487 dbg_msg(sys: "socket", fmt: "Setting V6ONLY failed: %d", errno);
1488 }
1489#endif
1490
1491 /* bind the socket */
1492 e = bind(fd: sock, addr: addr, len: sockaddrlen);
1493 if(e != 0)
1494 {
1495#if defined(CONF_FAMILY_WINDOWS)
1496 int error = WSAGetLastError();
1497 const std::string message = windows_format_system_message(error);
1498 dbg_msg("net", "failed to bind socket with domain %d and type %d (%d '%s')", domain, type, error, message.c_str());
1499#else
1500 dbg_msg(sys: "net", fmt: "failed to bind socket with domain %d and type %d (%d '%s')", domain, type, errno, strerror(errno));
1501#endif
1502 priv_net_close_socket(sock);
1503 return -1;
1504 }
1505
1506 /* return the newly created socket */
1507 return sock;
1508}
1509
1510int net_socket_type(NETSOCKET sock)
1511{
1512 return sock->type;
1513}
1514
1515NETSOCKET net_udp_create(NETADDR bindaddr)
1516{
1517 NETSOCKET sock = (NETSOCKET_INTERNAL *)malloc(size: sizeof(*sock));
1518 *sock = invalid_socket;
1519 NETADDR tmpbindaddr = bindaddr;
1520 int broadcast = 1;
1521 int socket = -1;
1522
1523 if(bindaddr.type & NETTYPE_IPV4)
1524 {
1525 struct sockaddr_in addr;
1526
1527 /* bind, we should check for error */
1528 tmpbindaddr.type = NETTYPE_IPV4;
1529 netaddr_to_sockaddr_in(src: &tmpbindaddr, dest: &addr);
1530 socket = priv_net_create_socket(AF_INET, SOCK_DGRAM, addr: (struct sockaddr *)&addr, sockaddrlen: sizeof(addr));
1531 if(socket >= 0)
1532 {
1533 sock->type |= NETTYPE_IPV4;
1534 sock->ipv4sock = socket;
1535
1536 /* set broadcast */
1537 if(setsockopt(fd: socket, SOL_SOCKET, SO_BROADCAST, optval: (const char *)&broadcast, optlen: sizeof(broadcast)) != 0)
1538 {
1539 dbg_msg(sys: "socket", fmt: "Setting BROADCAST on ipv4 failed: %d", net_errno());
1540 }
1541
1542 {
1543 /* set DSCP/TOS */
1544 int iptos = 0x10 /* IPTOS_LOWDELAY */;
1545 if(setsockopt(fd: socket, IPPROTO_IP, IP_TOS, optval: (char *)&iptos, optlen: sizeof(iptos)) != 0)
1546 {
1547 dbg_msg(sys: "socket", fmt: "Setting TOS on ipv4 failed: %d", net_errno());
1548 }
1549 }
1550 }
1551 }
1552#if defined(CONF_WEBSOCKETS)
1553 if(bindaddr.type & NETTYPE_WEBSOCKET_IPV4)
1554 {
1555 char addr_str[NETADDR_MAXSTRSIZE];
1556
1557 /* bind, we should check for error */
1558 tmpbindaddr.type = NETTYPE_WEBSOCKET_IPV4;
1559
1560 net_addr_str(addr: &tmpbindaddr, string: addr_str, max_length: sizeof(addr_str), add_port: 0);
1561 socket = websocket_create(addr: addr_str, port: tmpbindaddr.port);
1562
1563 if(socket >= 0)
1564 {
1565 sock->type |= NETTYPE_WEBSOCKET_IPV4;
1566 sock->web_ipv4sock = socket;
1567 }
1568 }
1569#endif
1570
1571 if(bindaddr.type & NETTYPE_IPV6)
1572 {
1573 struct sockaddr_in6 addr;
1574
1575 /* bind, we should check for error */
1576 tmpbindaddr.type = NETTYPE_IPV6;
1577 netaddr_to_sockaddr_in6(src: &tmpbindaddr, dest: &addr);
1578 socket = priv_net_create_socket(AF_INET6, SOCK_DGRAM, addr: (struct sockaddr *)&addr, sockaddrlen: sizeof(addr));
1579 if(socket >= 0)
1580 {
1581 sock->type |= NETTYPE_IPV6;
1582 sock->ipv6sock = socket;
1583
1584 /* set broadcast */
1585 if(setsockopt(fd: socket, SOL_SOCKET, SO_BROADCAST, optval: (const char *)&broadcast, optlen: sizeof(broadcast)) != 0)
1586 {
1587 dbg_msg(sys: "socket", fmt: "Setting BROADCAST on ipv6 failed: %d", net_errno());
1588 }
1589
1590 // TODO: setting IP_TOS on ipv6 with setsockopt is not supported on Windows, see https://github.com/ddnet/ddnet/issues/7605
1591#if !defined(CONF_FAMILY_WINDOWS)
1592 {
1593 /* set DSCP/TOS */
1594 int iptos = 0x10 /* IPTOS_LOWDELAY */;
1595 if(setsockopt(fd: socket, IPPROTO_IP, IP_TOS, optval: (char *)&iptos, optlen: sizeof(iptos)) != 0)
1596 {
1597 dbg_msg(sys: "socket", fmt: "Setting TOS on ipv6 failed: %d", net_errno());
1598 }
1599 }
1600#endif
1601 }
1602 }
1603
1604 if(socket < 0)
1605 {
1606 free(ptr: sock);
1607 sock = nullptr;
1608 }
1609 else
1610 {
1611 /* set non-blocking */
1612 net_set_non_blocking(sock);
1613
1614 net_buffer_init(buffer: &sock->buffer);
1615 }
1616
1617 /* return */
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 struct 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 netaddr_to_sockaddr_in(src: addr, dest: &sa);
1639
1640 d = sendto(fd: (int)sock->ipv4sock, buf: (const char *)data, n: size, flags: 0, addr: (struct sockaddr *)&sa, addr_len: sizeof(sa));
1641 }
1642 else
1643 dbg_msg(sys: "net", fmt: "can't send ipv4 traffic to this socket");
1644 }
1645
1646#if defined(CONF_WEBSOCKETS)
1647 if(addr->type & NETTYPE_WEBSOCKET_IPV4)
1648 {
1649 if(sock->web_ipv4sock >= 0)
1650 {
1651 char addr_str[NETADDR_MAXSTRSIZE];
1652 str_format(buffer: addr_str, buffer_size: sizeof(addr_str), format: "%d.%d.%d.%d", addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3]);
1653 d = websocket_send(socket: sock->web_ipv4sock, data: (const unsigned char *)data, size, addr_str, port: addr->port);
1654 }
1655
1656 else
1657 dbg_msg(sys: "net", fmt: "can't send websocket_ipv4 traffic to this socket");
1658 }
1659#endif
1660
1661 if(addr->type & NETTYPE_IPV6)
1662 {
1663 if(sock->ipv6sock >= 0)
1664 {
1665 struct sockaddr_in6 sa;
1666 if(addr->type & NETTYPE_LINK_BROADCAST)
1667 {
1668 mem_zero(block: &sa, size: sizeof(sa));
1669 sa.sin6_port = htons(hostshort: addr->port);
1670 sa.sin6_family = AF_INET6;
1671 sa.sin6_addr.s6_addr[0] = 0xff; /* multicast */
1672 sa.sin6_addr.s6_addr[1] = 0x02; /* link local scope */
1673 sa.sin6_addr.s6_addr[15] = 1; /* all nodes */
1674 }
1675 else
1676 netaddr_to_sockaddr_in6(src: addr, dest: &sa);
1677
1678 d = sendto(fd: (int)sock->ipv6sock, buf: (const char *)data, n: size, flags: 0, addr: (struct sockaddr *)&sa, addr_len: sizeof(sa));
1679 }
1680 else
1681 dbg_msg(sys: "net", fmt: "can't send ipv6 traffic to this socket");
1682 }
1683 /*
1684 else
1685 dbg_msg("net", "can't send to network of type %d", addr->type);
1686 */
1687
1688 /*if(d < 0)
1689 {
1690 char addrstr[256];
1691 net_addr_str(addr, addrstr, sizeof(addrstr));
1692
1693 dbg_msg("net", "sendto error (%d '%s')", errno, strerror(errno));
1694 dbg_msg("net", "\tsock = %d %x", sock, sock);
1695 dbg_msg("net", "\tsize = %d %x", size, size);
1696 dbg_msg("net", "\taddr = %s", addrstr);
1697
1698 }*/
1699 network_stats.sent_bytes += size;
1700 network_stats.sent_packets++;
1701 return d;
1702}
1703
1704void net_buffer_init(NETSOCKET_BUFFER *buffer)
1705{
1706#if defined(CONF_PLATFORM_LINUX)
1707 int i;
1708 buffer->pos = 0;
1709 buffer->size = 0;
1710 mem_zero(block: buffer->msgs, size: sizeof(buffer->msgs));
1711 mem_zero(block: buffer->iovecs, size: sizeof(buffer->iovecs));
1712 mem_zero(block: buffer->sockaddrs, size: sizeof(buffer->sockaddrs));
1713 for(i = 0; i < VLEN; ++i)
1714 {
1715 buffer->iovecs[i].iov_base = buffer->bufs[i];
1716 buffer->iovecs[i].iov_len = PACKETSIZE;
1717 buffer->msgs[i].msg_hdr.msg_iov = &(buffer->iovecs[i]);
1718 buffer->msgs[i].msg_hdr.msg_iovlen = 1;
1719 buffer->msgs[i].msg_hdr.msg_name = &(buffer->sockaddrs[i]);
1720 buffer->msgs[i].msg_hdr.msg_namelen = sizeof(buffer->sockaddrs[i]);
1721 }
1722#endif
1723}
1724
1725void net_buffer_reinit(NETSOCKET_BUFFER *buffer)
1726{
1727#if defined(CONF_PLATFORM_LINUX)
1728 for(int i = 0; i < VLEN; i++)
1729 {
1730 buffer->msgs[i].msg_hdr.msg_namelen = sizeof(buffer->sockaddrs[i]);
1731 }
1732#endif
1733}
1734
1735void net_buffer_simple(NETSOCKET_BUFFER *buffer, char **buf, int *size)
1736{
1737#if defined(CONF_PLATFORM_LINUX)
1738 *buf = buffer->bufs[0];
1739 *size = sizeof(buffer->bufs[0]);
1740#else
1741 *buf = buffer->buf;
1742 *size = sizeof(buffer->buf);
1743#endif
1744}
1745
1746int net_udp_recv(NETSOCKET sock, NETADDR *addr, unsigned char **data)
1747{
1748 char sockaddrbuf[128];
1749 int bytes = 0;
1750
1751#if defined(CONF_PLATFORM_LINUX)
1752 if(sock->ipv4sock >= 0)
1753 {
1754 if(sock->buffer.pos >= sock->buffer.size)
1755 {
1756 net_buffer_reinit(buffer: &sock->buffer);
1757 sock->buffer.size = recvmmsg(fd: sock->ipv4sock, vmessages: sock->buffer.msgs, VLEN, flags: 0, NULL);
1758 sock->buffer.pos = 0;
1759 }
1760 }
1761
1762 if(sock->ipv6sock >= 0)
1763 {
1764 if(sock->buffer.pos >= sock->buffer.size)
1765 {
1766 net_buffer_reinit(buffer: &sock->buffer);
1767 sock->buffer.size = recvmmsg(fd: sock->ipv6sock, vmessages: sock->buffer.msgs, VLEN, flags: 0, NULL);
1768 sock->buffer.pos = 0;
1769 }
1770 }
1771
1772 if(sock->buffer.pos < sock->buffer.size)
1773 {
1774 sockaddr_to_netaddr(src: (struct sockaddr *)&(sock->buffer.sockaddrs[sock->buffer.pos]), dst: addr);
1775 bytes = sock->buffer.msgs[sock->buffer.pos].msg_len;
1776 *data = (unsigned char *)sock->buffer.bufs[sock->buffer.pos];
1777 sock->buffer.pos++;
1778 network_stats.recv_bytes += bytes;
1779 network_stats.recv_packets++;
1780 return bytes;
1781 }
1782#else
1783 if(sock->ipv4sock >= 0)
1784 {
1785 socklen_t fromlen = sizeof(struct sockaddr_in);
1786 bytes = recvfrom(sock->ipv4sock, sock->buffer.buf, sizeof(sock->buffer.buf), 0, (struct sockaddr *)&sockaddrbuf, &fromlen);
1787 *data = (unsigned char *)sock->buffer.buf;
1788 }
1789
1790 if(bytes <= 0 && sock->ipv6sock >= 0)
1791 {
1792 socklen_t fromlen = sizeof(struct sockaddr_in6);
1793 bytes = recvfrom(sock->ipv6sock, sock->buffer.buf, sizeof(sock->buffer.buf), 0, (struct sockaddr *)&sockaddrbuf, &fromlen);
1794 *data = (unsigned char *)sock->buffer.buf;
1795 }
1796#endif
1797
1798#if defined(CONF_WEBSOCKETS)
1799 if(bytes <= 0 && sock->web_ipv4sock >= 0)
1800 {
1801 char *buf;
1802 int size;
1803 net_buffer_simple(buffer: &sock->buffer, buf: &buf, size: &size);
1804 socklen_t fromlen = sizeof(struct sockaddr);
1805 struct sockaddr_in *sockaddrbuf_in = (struct sockaddr_in *)&sockaddrbuf;
1806 bytes = websocket_recv(socket: sock->web_ipv4sock, data: (unsigned char *)buf, maxsize: size, sockaddrbuf: sockaddrbuf_in, fromLen: fromlen);
1807 *data = (unsigned char *)buf;
1808 sockaddrbuf_in->sin_family = AF_WEBSOCKET_INET;
1809 }
1810#endif
1811
1812 if(bytes > 0)
1813 {
1814 sockaddr_to_netaddr(src: (struct sockaddr *)&sockaddrbuf, dst: addr);
1815 network_stats.recv_bytes += bytes;
1816 network_stats.recv_packets++;
1817 return bytes;
1818 }
1819 else if(bytes == 0)
1820 return 0;
1821 return -1; /* error */
1822}
1823
1824int net_udp_close(NETSOCKET sock)
1825{
1826 return priv_net_close_all_sockets(sock);
1827}
1828
1829NETSOCKET net_tcp_create(NETADDR bindaddr)
1830{
1831 NETSOCKET sock = (NETSOCKET_INTERNAL *)malloc(size: sizeof(*sock));
1832 *sock = invalid_socket;
1833 NETADDR tmpbindaddr = bindaddr;
1834 int socket4 = -1;
1835
1836 if(bindaddr.type & NETTYPE_IPV4)
1837 {
1838 struct sockaddr_in addr;
1839
1840 /* bind, we should check for error */
1841 tmpbindaddr.type = NETTYPE_IPV4;
1842 netaddr_to_sockaddr_in(src: &tmpbindaddr, dest: &addr);
1843 socket4 = priv_net_create_socket(AF_INET, SOCK_STREAM, addr: (struct sockaddr *)&addr, sockaddrlen: sizeof(addr));
1844 if(socket4 >= 0)
1845 {
1846 sock->type |= NETTYPE_IPV4;
1847 sock->ipv4sock = socket4;
1848 }
1849 }
1850
1851 int socket6 = -1;
1852 if(bindaddr.type & NETTYPE_IPV6)
1853 {
1854 struct sockaddr_in6 addr;
1855
1856 /* bind, we should check for error */
1857 tmpbindaddr.type = NETTYPE_IPV6;
1858 netaddr_to_sockaddr_in6(src: &tmpbindaddr, dest: &addr);
1859 socket6 = priv_net_create_socket(AF_INET6, SOCK_STREAM, addr: (struct sockaddr *)&addr, sockaddrlen: sizeof(addr));
1860 if(socket6 >= 0)
1861 {
1862 sock->type |= NETTYPE_IPV6;
1863 sock->ipv6sock = socket6;
1864 }
1865 }
1866
1867 if(socket4 < 0 && socket6 < 0)
1868 {
1869 free(ptr: sock);
1870 sock = nullptr;
1871 }
1872
1873 /* return */
1874 return sock;
1875}
1876
1877static int net_set_blocking_impl(NETSOCKET sock, bool blocking)
1878{
1879 unsigned long mode = blocking ? 0 : 1;
1880 const char *mode_str = blocking ? "blocking" : "non-blocking";
1881 int sockets[] = {sock->ipv4sock, sock->ipv6sock};
1882 const char *socket_str[] = {"ipv4", "ipv6"};
1883
1884 for(size_t i = 0; i < std::size(sockets); ++i)
1885 {
1886 if(sockets[i] >= 0)
1887 {
1888#if defined(CONF_FAMILY_WINDOWS)
1889 int result = ioctlsocket(sockets[i], FIONBIO, (unsigned long *)&mode);
1890 if(result != NO_ERROR)
1891 dbg_msg("socket", "setting %s %s failed: %d", socket_str[i], mode_str, result);
1892#else
1893 if(ioctl(fd: sockets[i], FIONBIO, (unsigned long *)&mode) == -1)
1894 dbg_msg(sys: "socket", fmt: "setting %s %s failed: %d", socket_str[i], mode_str, errno);
1895#endif
1896 }
1897 }
1898
1899 return 0;
1900}
1901
1902int net_set_non_blocking(NETSOCKET sock)
1903{
1904 return net_set_blocking_impl(sock, blocking: false);
1905}
1906
1907int net_set_blocking(NETSOCKET sock)
1908{
1909 return net_set_blocking_impl(sock, blocking: true);
1910}
1911
1912int net_tcp_listen(NETSOCKET sock, int backlog)
1913{
1914 int err = -1;
1915 if(sock->ipv4sock >= 0)
1916 err = listen(fd: sock->ipv4sock, n: backlog);
1917 if(sock->ipv6sock >= 0)
1918 err = listen(fd: sock->ipv6sock, n: backlog);
1919 return err;
1920}
1921
1922int net_tcp_accept(NETSOCKET sock, NETSOCKET *new_sock, NETADDR *a)
1923{
1924 int s;
1925 socklen_t sockaddr_len;
1926
1927 *new_sock = nullptr;
1928
1929 if(sock->ipv4sock >= 0)
1930 {
1931 struct sockaddr_in addr;
1932 sockaddr_len = sizeof(addr);
1933
1934 s = accept(fd: sock->ipv4sock, addr: (struct sockaddr *)&addr, addr_len: &sockaddr_len);
1935
1936 if(s != -1)
1937 {
1938 sockaddr_to_netaddr(src: (const struct sockaddr *)&addr, dst: a);
1939
1940 *new_sock = (NETSOCKET_INTERNAL *)malloc(size: sizeof(**new_sock));
1941 **new_sock = invalid_socket;
1942 (*new_sock)->type = NETTYPE_IPV4;
1943 (*new_sock)->ipv4sock = s;
1944 return s;
1945 }
1946 }
1947
1948 if(sock->ipv6sock >= 0)
1949 {
1950 struct sockaddr_in6 addr;
1951 sockaddr_len = sizeof(addr);
1952
1953 s = accept(fd: sock->ipv6sock, addr: (struct sockaddr *)&addr, addr_len: &sockaddr_len);
1954
1955 if(s != -1)
1956 {
1957 *new_sock = (NETSOCKET_INTERNAL *)malloc(size: sizeof(**new_sock));
1958 **new_sock = invalid_socket;
1959 sockaddr_to_netaddr(src: (const struct sockaddr *)&addr, dst: a);
1960 (*new_sock)->type = NETTYPE_IPV6;
1961 (*new_sock)->ipv6sock = s;
1962 return s;
1963 }
1964 }
1965
1966 return -1;
1967}
1968
1969int net_tcp_connect(NETSOCKET sock, const NETADDR *a)
1970{
1971 if(a->type & NETTYPE_IPV4)
1972 {
1973 struct sockaddr_in addr;
1974 netaddr_to_sockaddr_in(src: a, dest: &addr);
1975 if(sock->ipv4sock < 0)
1976 return -2;
1977 return connect(fd: sock->ipv4sock, addr: (struct sockaddr *)&addr, len: sizeof(addr));
1978 }
1979
1980 if(a->type & NETTYPE_IPV6)
1981 {
1982 struct sockaddr_in6 addr;
1983 netaddr_to_sockaddr_in6(src: a, dest: &addr);
1984 if(sock->ipv6sock < 0)
1985 return -2;
1986 return connect(fd: sock->ipv6sock, addr: (struct sockaddr *)&addr, len: sizeof(addr));
1987 }
1988
1989 return -1;
1990}
1991
1992int net_tcp_connect_non_blocking(NETSOCKET sock, NETADDR bindaddr)
1993{
1994 int res = 0;
1995
1996 net_set_non_blocking(sock);
1997 res = net_tcp_connect(sock, a: &bindaddr);
1998 net_set_blocking(sock);
1999
2000 return res;
2001}
2002
2003int net_tcp_send(NETSOCKET sock, const void *data, int size)
2004{
2005 int bytes = -1;
2006
2007 if(sock->ipv4sock >= 0)
2008 bytes = send(fd: (int)sock->ipv4sock, buf: (const char *)data, n: size, flags: 0);
2009 if(sock->ipv6sock >= 0)
2010 bytes = send(fd: (int)sock->ipv6sock, buf: (const char *)data, n: size, flags: 0);
2011
2012 return bytes;
2013}
2014
2015int net_tcp_recv(NETSOCKET sock, void *data, int maxsize)
2016{
2017 int bytes = -1;
2018
2019 if(sock->ipv4sock >= 0)
2020 bytes = recv(fd: (int)sock->ipv4sock, buf: (char *)data, n: maxsize, flags: 0);
2021 if(sock->ipv6sock >= 0)
2022 bytes = recv(fd: (int)sock->ipv6sock, buf: (char *)data, n: maxsize, flags: 0);
2023
2024 return bytes;
2025}
2026
2027int net_tcp_close(NETSOCKET sock)
2028{
2029 return priv_net_close_all_sockets(sock);
2030}
2031
2032int net_errno()
2033{
2034#if defined(CONF_FAMILY_WINDOWS)
2035 return WSAGetLastError();
2036#else
2037 return errno;
2038#endif
2039}
2040
2041int net_would_block()
2042{
2043#if defined(CONF_FAMILY_WINDOWS)
2044 return net_errno() == WSAEWOULDBLOCK;
2045#else
2046 return net_errno() == EWOULDBLOCK;
2047#endif
2048}
2049
2050void net_init()
2051{
2052#if defined(CONF_FAMILY_WINDOWS)
2053 WSADATA wsa_data;
2054 dbg_assert(WSAStartup(MAKEWORD(1, 1), &wsa_data) == 0, "network initialization failed.");
2055#endif
2056}
2057
2058#if defined(CONF_FAMILY_UNIX)
2059UNIXSOCKET net_unix_create_unnamed()
2060{
2061 return socket(AF_UNIX, SOCK_DGRAM, protocol: 0);
2062}
2063
2064int net_unix_send(UNIXSOCKET sock, UNIXSOCKETADDR *addr, void *data, int size)
2065{
2066 return sendto(fd: sock, buf: data, n: size, flags: 0, addr: (struct sockaddr *)addr, addr_len: sizeof(struct sockaddr_un));
2067}
2068
2069void net_unix_set_addr(UNIXSOCKETADDR *addr, const char *path)
2070{
2071 mem_zero(block: addr, size: sizeof(*addr));
2072 addr->sun_family = AF_UNIX;
2073 str_copy(dst&: addr->sun_path, src: path);
2074}
2075
2076void net_unix_close(UNIXSOCKET sock)
2077{
2078 close(fd: sock);
2079}
2080#endif
2081
2082#if defined(CONF_FAMILY_WINDOWS)
2083static inline time_t filetime_to_unixtime(LPFILETIME filetime)
2084{
2085 time_t t;
2086 ULARGE_INTEGER li;
2087 li.LowPart = filetime->dwLowDateTime;
2088 li.HighPart = filetime->dwHighDateTime;
2089
2090 li.QuadPart /= 10000000; // 100ns to 1s
2091 li.QuadPart -= 11644473600LL; // Windows epoch is in the past
2092
2093 t = li.QuadPart;
2094 return t == (time_t)li.QuadPart ? t : (time_t)-1;
2095}
2096#endif
2097
2098void fs_listdir(const char *dir, FS_LISTDIR_CALLBACK cb, int type, void *user)
2099{
2100#if defined(CONF_FAMILY_WINDOWS)
2101 char buffer[IO_MAX_PATH_LENGTH];
2102 str_format(buffer, sizeof(buffer), "%s/*", dir);
2103 const std::wstring wide_buffer = windows_utf8_to_wide(buffer);
2104
2105 WIN32_FIND_DATAW finddata;
2106 HANDLE handle = FindFirstFileW(wide_buffer.c_str(), &finddata);
2107 if(handle == INVALID_HANDLE_VALUE)
2108 return;
2109
2110 do
2111 {
2112 const std::optional<std::string> current_entry = windows_wide_to_utf8(finddata.cFileName);
2113 if(!current_entry.has_value())
2114 {
2115 log_error("filesystem", "ERROR: file/folder name containing invalid UTF-16 found in folder '%s'", dir);
2116 continue;
2117 }
2118 if(cb(current_entry.value().c_str(), (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0, type, user))
2119 break;
2120 } while(FindNextFileW(handle, &finddata));
2121
2122 FindClose(handle);
2123#else
2124 DIR *dir_handle = opendir(name: dir);
2125 if(dir_handle == nullptr)
2126 return;
2127
2128 char buffer[IO_MAX_PATH_LENGTH];
2129 str_format(buffer, buffer_size: sizeof(buffer), format: "%s/", dir);
2130 size_t length = str_length(str: buffer);
2131 while(true)
2132 {
2133 struct dirent *entry = readdir(dirp: dir_handle);
2134 if(entry == nullptr)
2135 break;
2136 if(!str_utf8_check(str: entry->d_name))
2137 {
2138 log_error("filesystem", "ERROR: file/folder name containing invalid UTF-8 found in folder '%s'", dir);
2139 continue;
2140 }
2141 str_copy(dst: buffer + length, src: entry->d_name, dst_size: sizeof(buffer) - length);
2142 if(cb(entry->d_name, fs_is_dir(path: buffer), type, user))
2143 break;
2144 }
2145
2146 closedir(dirp: dir_handle);
2147#endif
2148}
2149
2150void fs_listdir_fileinfo(const char *dir, FS_LISTDIR_CALLBACK_FILEINFO cb, int type, void *user)
2151{
2152#if defined(CONF_FAMILY_WINDOWS)
2153 char buffer[IO_MAX_PATH_LENGTH];
2154 str_format(buffer, sizeof(buffer), "%s/*", dir);
2155 const std::wstring wide_buffer = windows_utf8_to_wide(buffer);
2156
2157 WIN32_FIND_DATAW finddata;
2158 HANDLE handle = FindFirstFileW(wide_buffer.c_str(), &finddata);
2159 if(handle == INVALID_HANDLE_VALUE)
2160 return;
2161
2162 do
2163 {
2164 const std::optional<std::string> current_entry = windows_wide_to_utf8(finddata.cFileName);
2165 if(!current_entry.has_value())
2166 {
2167 log_error("filesystem", "ERROR: file/folder name containing invalid UTF-16 found in folder '%s'", dir);
2168 continue;
2169 }
2170
2171 CFsFileInfo info;
2172 info.m_pName = current_entry.value().c_str();
2173 info.m_TimeCreated = filetime_to_unixtime(&finddata.ftCreationTime);
2174 info.m_TimeModified = filetime_to_unixtime(&finddata.ftLastWriteTime);
2175
2176 if(cb(&info, (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0, type, user))
2177 break;
2178 } while(FindNextFileW(handle, &finddata));
2179
2180 FindClose(handle);
2181#else
2182 DIR *dir_handle = opendir(name: dir);
2183 if(dir_handle == nullptr)
2184 return;
2185
2186 char buffer[IO_MAX_PATH_LENGTH];
2187 str_format(buffer, buffer_size: sizeof(buffer), format: "%s/", dir);
2188 size_t length = str_length(str: buffer);
2189
2190 while(true)
2191 {
2192 struct dirent *entry = readdir(dirp: dir_handle);
2193 if(entry == nullptr)
2194 break;
2195 if(!str_utf8_check(str: entry->d_name))
2196 {
2197 log_error("filesystem", "ERROR: file/folder name containing invalid UTF-8 found in folder '%s'", dir);
2198 continue;
2199 }
2200 str_copy(dst: buffer + length, src: entry->d_name, dst_size: sizeof(buffer) - length);
2201 time_t created = -1, modified = -1;
2202 fs_file_time(name: buffer, created: &created, modified: &modified);
2203
2204 CFsFileInfo info;
2205 info.m_pName = entry->d_name;
2206 info.m_TimeCreated = created;
2207 info.m_TimeModified = modified;
2208
2209 if(cb(&info, fs_is_dir(path: buffer), type, user))
2210 break;
2211 }
2212
2213 closedir(dirp: dir_handle);
2214#endif
2215}
2216
2217int fs_storage_path(const char *appname, char *path, int max)
2218{
2219#if defined(CONF_FAMILY_WINDOWS)
2220 WCHAR *wide_home = _wgetenv(L"APPDATA");
2221 if(!wide_home)
2222 {
2223 path[0] = '\0';
2224 return -1;
2225 }
2226 const std::optional<std::string> home = windows_wide_to_utf8(wide_home);
2227 if(!home.has_value())
2228 {
2229 log_error("filesystem", "ERROR: the APPDATA environment variable contains invalid UTF-16");
2230 path[0] = '\0';
2231 return -1;
2232 }
2233 str_format(path, max, "%s/%s", home.value().c_str(), appname);
2234 return 0;
2235#else
2236 char *home = getenv(name: "HOME");
2237 if(!home)
2238 {
2239 path[0] = '\0';
2240 return -1;
2241 }
2242
2243 if(!str_utf8_check(str: home))
2244 {
2245 log_error("filesystem", "ERROR: the HOME environment variable contains invalid UTF-8");
2246 path[0] = '\0';
2247 return -1;
2248 }
2249
2250#if defined(CONF_PLATFORM_HAIKU)
2251 str_format(path, max, "%s/config/settings/%s", home, appname);
2252#elif defined(CONF_PLATFORM_MACOS)
2253 str_format(path, max, "%s/Library/Application Support/%s", home, appname);
2254#else
2255 if(str_comp(a: appname, b: "Teeworlds") == 0)
2256 {
2257 // fallback for old directory for Teeworlds compatibility
2258 str_format(buffer: path, buffer_size: max, format: "%s/.%s", home, appname);
2259 }
2260 else
2261 {
2262 char *data_home = getenv(name: "XDG_DATA_HOME");
2263 if(data_home)
2264 {
2265 if(!str_utf8_check(str: data_home))
2266 {
2267 log_error("filesystem", "ERROR: the XDG_DATA_HOME environment variable contains invalid UTF-8");
2268 path[0] = '\0';
2269 return -1;
2270 }
2271 str_format(buffer: path, buffer_size: max, format: "%s/%s", data_home, appname);
2272 }
2273 else
2274 str_format(buffer: path, buffer_size: max, format: "%s/.local/share/%s", home, appname);
2275 }
2276 for(int i = str_length(str: path) - str_length(str: appname); path[i]; i++)
2277 path[i] = tolower(c: (unsigned char)path[i]);
2278#endif
2279
2280 return 0;
2281#endif
2282}
2283
2284int fs_makedir_rec_for(const char *path)
2285{
2286 char buffer[IO_MAX_PATH_LENGTH];
2287 str_copy(dst&: buffer, src: path);
2288 for(int index = 1; buffer[index] != '\0'; ++index)
2289 {
2290 // Do not try to create folder for drive letters on Windows,
2291 // as this is not necessary and may fail for system drives.
2292 if((buffer[index] == '/' || buffer[index] == '\\') && buffer[index + 1] != '\0' && buffer[index - 1] != ':')
2293 {
2294 buffer[index] = '\0';
2295 if(fs_makedir(path: buffer) < 0)
2296 {
2297 return -1;
2298 }
2299 buffer[index] = '/';
2300 }
2301 }
2302 return 0;
2303}
2304
2305int fs_makedir(const char *path)
2306{
2307#if defined(CONF_FAMILY_WINDOWS)
2308 const std::wstring wide_path = windows_utf8_to_wide(path);
2309 if(CreateDirectoryW(wide_path.c_str(), NULL) != 0)
2310 return 0;
2311 if(GetLastError() == ERROR_ALREADY_EXISTS)
2312 return 0;
2313 return -1;
2314#else
2315#ifdef CONF_PLATFORM_HAIKU
2316 struct stat st;
2317 if(stat(path, &st) == 0)
2318 return 0;
2319#endif
2320 if(mkdir(path: path, mode: 0755) == 0)
2321 return 0;
2322 if(errno == EEXIST)
2323 return 0;
2324 return -1;
2325#endif
2326}
2327
2328int fs_removedir(const char *path)
2329{
2330#if defined(CONF_FAMILY_WINDOWS)
2331 const std::wstring wide_path = windows_utf8_to_wide(path);
2332 if(RemoveDirectoryW(wide_path.c_str()) != 0)
2333 return 0;
2334 return -1;
2335#else
2336 if(rmdir(path: path) == 0)
2337 return 0;
2338 return -1;
2339#endif
2340}
2341
2342int fs_is_file(const char *path)
2343{
2344#if defined(CONF_FAMILY_WINDOWS)
2345 const std::wstring wide_path = windows_utf8_to_wide(path);
2346 DWORD attributes = GetFileAttributesW(wide_path.c_str());
2347 return attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0;
2348#else
2349 struct stat sb;
2350 if(stat(file: path, buf: &sb) == -1)
2351 return 0;
2352 return S_ISREG(sb.st_mode) ? 1 : 0;
2353#endif
2354}
2355
2356int fs_is_dir(const char *path)
2357{
2358#if defined(CONF_FAMILY_WINDOWS)
2359 const std::wstring wide_path = windows_utf8_to_wide(path);
2360 DWORD attributes = GetFileAttributesW(wide_path.c_str());
2361 return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0;
2362#else
2363 struct stat sb;
2364 if(stat(file: path, buf: &sb) == -1)
2365 return 0;
2366 return S_ISDIR(sb.st_mode) ? 1 : 0;
2367#endif
2368}
2369
2370int fs_is_relative_path(const char *path)
2371{
2372#if defined(CONF_FAMILY_WINDOWS)
2373 const std::wstring wide_path = windows_utf8_to_wide(path);
2374 return PathIsRelativeW(wide_path.c_str()) ? 1 : 0;
2375#else
2376 return path[0] == '/' ? 0 : 1; // yes, it's that simple
2377#endif
2378}
2379
2380int fs_chdir(const char *path)
2381{
2382#if defined(CONF_FAMILY_WINDOWS)
2383 const std::wstring wide_path = windows_utf8_to_wide(path);
2384 return SetCurrentDirectoryW(wide_path.c_str()) != 0 ? 0 : 1;
2385#else
2386 return chdir(path: path) ? 1 : 0;
2387#endif
2388}
2389
2390char *fs_getcwd(char *buffer, int buffer_size)
2391{
2392#if defined(CONF_FAMILY_WINDOWS)
2393 const DWORD size_needed = GetCurrentDirectoryW(0, nullptr);
2394 std::wstring wide_current_dir(size_needed, L'0');
2395 dbg_assert(GetCurrentDirectoryW(size_needed, wide_current_dir.data()) == size_needed - 1, "GetCurrentDirectoryW failure");
2396 const std::optional<std::string> current_dir = windows_wide_to_utf8(wide_current_dir.c_str());
2397 if(!current_dir.has_value())
2398 {
2399 buffer[0] = '\0';
2400 return nullptr;
2401 }
2402 str_copy(buffer, current_dir.value().c_str(), buffer_size);
2403 return buffer;
2404#else
2405 char *result = getcwd(buf: buffer, size: buffer_size);
2406 if(result == nullptr || !str_utf8_check(str: result))
2407 {
2408 buffer[0] = '\0';
2409 return nullptr;
2410 }
2411 return result;
2412#endif
2413}
2414
2415const char *fs_filename(const char *path)
2416{
2417 for(const char *filename = path + str_length(str: path); filename >= path; --filename)
2418 {
2419 if(filename[0] == '/' || filename[0] == '\\')
2420 return filename + 1;
2421 }
2422 return path;
2423}
2424
2425void fs_split_file_extension(const char *filename, char *name, size_t name_size, char *extension, size_t extension_size)
2426{
2427 dbg_assert(name != nullptr || extension != nullptr, "name or extension parameter required");
2428 dbg_assert(name == nullptr || name_size > 0, "name_size invalid");
2429 dbg_assert(extension == nullptr || extension_size > 0, "extension_size invalid");
2430
2431 const char *last_dot = str_rchr(haystack: filename, needle: '.');
2432 if(last_dot == nullptr || last_dot == filename)
2433 {
2434 if(extension != nullptr)
2435 extension[0] = '\0';
2436 if(name != nullptr)
2437 str_copy(dst: name, src: filename, dst_size: name_size);
2438 }
2439 else
2440 {
2441 if(extension != nullptr)
2442 str_copy(dst: extension, src: last_dot + 1, dst_size: extension_size);
2443 if(name != nullptr)
2444 str_truncate(dst: name, dst_size: name_size, src: filename, truncation_len: last_dot - filename);
2445 }
2446}
2447
2448int fs_parent_dir(char *path)
2449{
2450 char *parent = 0;
2451 for(; *path; ++path)
2452 {
2453 if(*path == '/' || *path == '\\')
2454 parent = path;
2455 }
2456
2457 if(parent)
2458 {
2459 *parent = 0;
2460 return 0;
2461 }
2462 return 1;
2463}
2464
2465int fs_remove(const char *filename)
2466{
2467#if defined(CONF_FAMILY_WINDOWS)
2468 const std::wstring wide_filename = windows_utf8_to_wide(filename);
2469 return DeleteFileW(wide_filename.c_str()) == 0;
2470#else
2471 return unlink(name: filename) != 0;
2472#endif
2473}
2474
2475int fs_rename(const char *oldname, const char *newname)
2476{
2477#if defined(CONF_FAMILY_WINDOWS)
2478 const std::wstring wide_oldname = windows_utf8_to_wide(oldname);
2479 const std::wstring wide_newname = windows_utf8_to_wide(newname);
2480 if(MoveFileExW(wide_oldname.c_str(), wide_newname.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) == 0)
2481 return 1;
2482#else
2483 if(rename(old: oldname, new: newname) != 0)
2484 return 1;
2485#endif
2486 return 0;
2487}
2488
2489int fs_file_time(const char *name, time_t *created, time_t *modified)
2490{
2491#if defined(CONF_FAMILY_WINDOWS)
2492 WIN32_FIND_DATAW finddata;
2493 const std::wstring wide_name = windows_utf8_to_wide(name);
2494 HANDLE handle = FindFirstFileW(wide_name.c_str(), &finddata);
2495 if(handle == INVALID_HANDLE_VALUE)
2496 return 1;
2497
2498 *created = filetime_to_unixtime(&finddata.ftCreationTime);
2499 *modified = filetime_to_unixtime(&finddata.ftLastWriteTime);
2500 FindClose(handle);
2501#elif defined(CONF_FAMILY_UNIX)
2502 struct stat sb;
2503 if(stat(file: name, buf: &sb))
2504 return 1;
2505
2506 *created = sb.st_ctime;
2507 *modified = sb.st_mtime;
2508#else
2509#error not implemented
2510#endif
2511
2512 return 0;
2513}
2514
2515void swap_endian(void *data, unsigned elem_size, unsigned num)
2516{
2517 char *src = (char *)data;
2518 char *dst = src + (elem_size - 1);
2519
2520 while(num)
2521 {
2522 unsigned n = elem_size >> 1;
2523 char tmp;
2524 while(n)
2525 {
2526 tmp = *src;
2527 *src = *dst;
2528 *dst = tmp;
2529
2530 src++;
2531 dst--;
2532 n--;
2533 }
2534
2535 src = src + (elem_size >> 1);
2536 dst = src + (elem_size - 1);
2537 num--;
2538 }
2539}
2540
2541int net_socket_read_wait(NETSOCKET sock, int time)
2542{
2543 struct timeval tv;
2544 fd_set readfds;
2545 int sockid;
2546
2547 tv.tv_sec = time / 1000000;
2548 tv.tv_usec = time % 1000000;
2549 sockid = 0;
2550
2551 FD_ZERO(&readfds);
2552 if(sock->ipv4sock >= 0)
2553 {
2554 FD_SET(sock->ipv4sock, &readfds);
2555 sockid = sock->ipv4sock;
2556 }
2557 if(sock->ipv6sock >= 0)
2558 {
2559 FD_SET(sock->ipv6sock, &readfds);
2560 if(sock->ipv6sock > sockid)
2561 sockid = sock->ipv6sock;
2562 }
2563#if defined(CONF_WEBSOCKETS)
2564 if(sock->web_ipv4sock >= 0)
2565 {
2566 int maxfd = websocket_fd_set(socket: sock->web_ipv4sock, set: &readfds);
2567 if(maxfd > sockid)
2568 {
2569 sockid = maxfd;
2570 FD_SET(sockid, &readfds);
2571 }
2572 }
2573#endif
2574
2575 /* don't care about writefds and exceptfds */
2576 if(time < 0)
2577 select(nfds: sockid + 1, readfds: &readfds, NULL, NULL, NULL);
2578 else
2579 select(nfds: sockid + 1, readfds: &readfds, NULL, NULL, timeout: &tv);
2580
2581 if(sock->ipv4sock >= 0 && FD_ISSET(sock->ipv4sock, &readfds))
2582 return 1;
2583#if defined(CONF_WEBSOCKETS)
2584 if(sock->web_ipv4sock >= 0 && FD_ISSET(sockid, &readfds))
2585 return 1;
2586#endif
2587 if(sock->ipv6sock >= 0 && FD_ISSET(sock->ipv6sock, &readfds))
2588 return 1;
2589
2590 return 0;
2591}
2592
2593int64_t time_timestamp()
2594{
2595 return time(timer: 0);
2596}
2597
2598static struct tm *time_localtime_threadlocal(time_t *time_data)
2599{
2600#if defined(CONF_FAMILY_WINDOWS)
2601 // The result of localtime is thread-local on Windows
2602 // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-localtime32-localtime64
2603 return localtime(time_data);
2604#else
2605 // Thread-local buffer for the result of localtime_r
2606 thread_local struct tm time_info_buf;
2607 return localtime_r(timer: time_data, tp: &time_info_buf);
2608#endif
2609}
2610
2611int time_houroftheday()
2612{
2613 time_t time_data;
2614 time(timer: &time_data);
2615 struct tm *time_info = time_localtime_threadlocal(time_data: &time_data);
2616 return time_info->tm_hour;
2617}
2618
2619static bool time_iseasterday(time_t time_data, struct tm *time_info)
2620{
2621 // compute Easter day (Sunday) using https://en.wikipedia.org/w/index.php?title=Computus&oldid=890710285#Anonymous_Gregorian_algorithm
2622 int Y = time_info->tm_year + 1900;
2623 int a = Y % 19;
2624 int b = Y / 100;
2625 int c = Y % 100;
2626 int d = b / 4;
2627 int e = b % 4;
2628 int f = (b + 8) / 25;
2629 int g = (b - f + 1) / 3;
2630 int h = (19 * a + b - d - g + 15) % 30;
2631 int i = c / 4;
2632 int k = c % 4;
2633 int L = (32 + 2 * e + 2 * i - h - k) % 7;
2634 int m = (a + 11 * h + 22 * L) / 451;
2635 int month = (h + L - 7 * m + 114) / 31;
2636 int day = ((h + L - 7 * m + 114) % 31) + 1;
2637
2638 // (now-1d ≤ easter ≤ now+2d) <=> (easter-2d ≤ now ≤ easter+1d) <=> (Good Friday ≤ now ≤ Easter Monday)
2639 for(int day_offset = -1; day_offset <= 2; day_offset++)
2640 {
2641 time_data = time_data + day_offset * 60 * 60 * 24;
2642 time_info = time_localtime_threadlocal(time_data: &time_data);
2643 if(time_info->tm_mon == month - 1 && time_info->tm_mday == day)
2644 return true;
2645 }
2646 return false;
2647}
2648
2649ETimeSeason time_season()
2650{
2651 time_t time_data;
2652 time(timer: &time_data);
2653 struct tm *time_info = time_localtime_threadlocal(time_data: &time_data);
2654
2655 if((time_info->tm_mon == 11 && time_info->tm_mday == 31) || (time_info->tm_mon == 0 && time_info->tm_mday == 1))
2656 {
2657 return SEASON_NEWYEAR;
2658 }
2659 else if(time_info->tm_mon == 11 && time_info->tm_mday >= 24 && time_info->tm_mday <= 26)
2660 {
2661 return SEASON_XMAS;
2662 }
2663 else if((time_info->tm_mon == 9 && time_info->tm_mday == 31) || (time_info->tm_mon == 10 && time_info->tm_mday == 1))
2664 {
2665 return SEASON_HALLOWEEN;
2666 }
2667 else if(time_iseasterday(time_data, time_info))
2668 {
2669 return SEASON_EASTER;
2670 }
2671
2672 switch(time_info->tm_mon)
2673 {
2674 case 11:
2675 case 0:
2676 case 1:
2677 return SEASON_WINTER;
2678 case 2:
2679 case 3:
2680 case 4:
2681 return SEASON_SPRING;
2682 case 5:
2683 case 6:
2684 case 7:
2685 return SEASON_SUMMER;
2686 case 8:
2687 case 9:
2688 case 10:
2689 return SEASON_AUTUMN;
2690 default:
2691 dbg_assert(false, "Invalid month");
2692 dbg_break();
2693 }
2694}
2695
2696void str_append(char *dst, const char *src, int dst_size)
2697{
2698 int s = str_length(str: dst);
2699 int i = 0;
2700 while(s < dst_size)
2701 {
2702 dst[s] = src[i];
2703 if(!src[i]) /* check for null termination */
2704 break;
2705 s++;
2706 i++;
2707 }
2708
2709 dst[dst_size - 1] = 0; /* assure null termination */
2710 str_utf8_fix_truncation(str: dst);
2711}
2712
2713int str_copy(char *dst, const char *src, int dst_size)
2714{
2715 dst[0] = '\0';
2716 strncat(dest: dst, src: src, n: dst_size - 1);
2717 return str_utf8_fix_truncation(str: dst);
2718}
2719
2720void str_utf8_truncate(char *dst, int dst_size, const char *src, int truncation_len)
2721{
2722 int size = -1;
2723 const char *cursor = src;
2724 int pos = 0;
2725 while(pos <= truncation_len && cursor - src < dst_size && size != cursor - src)
2726 {
2727 size = cursor - src;
2728 if(str_utf8_decode(ptr: &cursor) == 0)
2729 {
2730 break;
2731 }
2732 pos++;
2733 }
2734 str_copy(dst, src, dst_size: size + 1);
2735}
2736
2737void str_truncate(char *dst, int dst_size, const char *src, int truncation_len)
2738{
2739 int size = dst_size;
2740 if(truncation_len < size)
2741 {
2742 size = truncation_len + 1;
2743 }
2744 str_copy(dst, src, dst_size: size);
2745}
2746
2747int str_length(const char *str)
2748{
2749 return (int)strlen(s: str);
2750}
2751
2752int str_format_v(char *buffer, int buffer_size, const char *format, va_list args)
2753{
2754#if defined(CONF_FAMILY_WINDOWS)
2755 _vsprintf_p(buffer, buffer_size, format, args);
2756 buffer[buffer_size - 1] = 0; /* assure null termination */
2757#else
2758 vsnprintf(s: buffer, maxlen: buffer_size, format: format, arg: args);
2759 /* null termination is assured by definition of vsnprintf */
2760#endif
2761 return str_utf8_fix_truncation(str: buffer);
2762}
2763
2764int str_format_int(char *buffer, size_t buffer_size, int value)
2765{
2766 buffer[0] = '\0'; // Fix false positive clang-analyzer-core.UndefinedBinaryOperatorResult when using result
2767 auto result = std::to_chars(first: buffer, last: buffer + buffer_size - 1, value: value);
2768 result.ptr[0] = '\0';
2769 return result.ptr - buffer;
2770}
2771
2772#undef str_format
2773int str_format(char *buffer, int buffer_size, const char *format, ...)
2774{
2775 va_list args;
2776 va_start(args, format);
2777 int length = str_format_v(buffer, buffer_size, format, args);
2778 va_end(args);
2779 return length;
2780}
2781#if !defined(CONF_DEBUG)
2782#define str_format str_format_opt
2783#endif
2784
2785const char *str_trim_words(const char *str, int words)
2786{
2787 while(*str && str_isspace(c: *str))
2788 str++;
2789 while(words && *str)
2790 {
2791 if(str_isspace(c: *str) && !str_isspace(c: *(str + 1)))
2792 words--;
2793 str++;
2794 }
2795 return str;
2796}
2797
2798bool str_has_cc(const char *str)
2799{
2800 unsigned char *s = (unsigned char *)str;
2801 while(*s)
2802 {
2803 if(*s < 32)
2804 {
2805 return true;
2806 }
2807 s++;
2808 }
2809 return false;
2810}
2811
2812/* makes sure that the string only contains the characters between 32 and 255 */
2813void str_sanitize_cc(char *str_in)
2814{
2815 unsigned char *str = (unsigned char *)str_in;
2816 while(*str)
2817 {
2818 if(*str < 32)
2819 *str = ' ';
2820 str++;
2821 }
2822}
2823
2824/* makes sure that the string only contains the characters between 32 and 255 + \r\n\t */
2825void str_sanitize(char *str_in)
2826{
2827 unsigned char *str = (unsigned char *)str_in;
2828 while(*str)
2829 {
2830 if(*str < 32 && !(*str == '\r') && !(*str == '\n') && !(*str == '\t'))
2831 *str = ' ';
2832 str++;
2833 }
2834}
2835
2836void str_sanitize_filename(char *str_in)
2837{
2838 unsigned char *str = (unsigned char *)str_in;
2839 while(*str)
2840 {
2841 if(*str < 32 || *str == '\\' || *str == '/' || *str == '|' || *str == ':' || *str == '*' || *str == '?' || *str == '<' || *str == '>' || *str == '"')
2842 *str = ' ';
2843 str++;
2844 }
2845}
2846
2847/* removes leading and trailing spaces and limits the use of multiple spaces */
2848void str_clean_whitespaces(char *str_in)
2849{
2850 char *read = str_in;
2851 char *write = str_in;
2852
2853 /* skip initial whitespace */
2854 while(*read == ' ')
2855 read++;
2856
2857 /* end of read string is detected in the loop */
2858 while(true)
2859 {
2860 /* skip whitespace */
2861 int found_whitespace = 0;
2862 for(; *read == ' '; read++)
2863 found_whitespace = 1;
2864 /* if not at the end of the string, put a found whitespace here */
2865 if(*read)
2866 {
2867 if(found_whitespace)
2868 *write++ = ' ';
2869 *write++ = *read++;
2870 }
2871 else
2872 {
2873 *write = 0;
2874 break;
2875 }
2876 }
2877}
2878
2879char *str_skip_to_whitespace(char *str)
2880{
2881 while(*str && !str_isspace(c: *str))
2882 str++;
2883 return str;
2884}
2885
2886const char *str_skip_to_whitespace_const(const char *str)
2887{
2888 while(*str && !str_isspace(c: *str))
2889 str++;
2890 return str;
2891}
2892
2893char *str_skip_whitespaces(char *str)
2894{
2895 while(*str && str_isspace(c: *str))
2896 str++;
2897 return str;
2898}
2899
2900const char *str_skip_whitespaces_const(const char *str)
2901{
2902 while(*str && str_isspace(c: *str))
2903 str++;
2904 return str;
2905}
2906
2907/* case */
2908int str_comp_nocase(const char *a, const char *b)
2909{
2910#if defined(CONF_FAMILY_WINDOWS)
2911 return _stricmp(a, b);
2912#else
2913 return strcasecmp(s1: a, s2: b);
2914#endif
2915}
2916
2917int str_comp_nocase_num(const char *a, const char *b, int num)
2918{
2919#if defined(CONF_FAMILY_WINDOWS)
2920 return _strnicmp(a, b, num);
2921#else
2922 return strncasecmp(s1: a, s2: b, n: num);
2923#endif
2924}
2925
2926int str_comp(const char *a, const char *b)
2927{
2928 return strcmp(s1: a, s2: b);
2929}
2930
2931int str_comp_num(const char *a, const char *b, int num)
2932{
2933 return strncmp(s1: a, s2: b, n: num);
2934}
2935
2936int str_comp_filenames(const char *a, const char *b)
2937{
2938 int result;
2939
2940 for(; *a && *b; ++a, ++b)
2941 {
2942 if(str_isnum(c: *a) && str_isnum(c: *b))
2943 {
2944 result = 0;
2945 do
2946 {
2947 if(!result)
2948 result = *a - *b;
2949 ++a;
2950 ++b;
2951 } while(str_isnum(c: *a) && str_isnum(c: *b));
2952
2953 if(str_isnum(c: *a))
2954 return 1;
2955 else if(str_isnum(c: *b))
2956 return -1;
2957 else if(result || *a == '\0' || *b == '\0')
2958 return result;
2959 }
2960
2961 result = tolower(c: *a) - tolower(c: *b);
2962 if(result)
2963 return result;
2964 }
2965 return *a - *b;
2966}
2967
2968const char *str_startswith_nocase(const char *str, const char *prefix)
2969{
2970 int prefixl = str_length(str: prefix);
2971 if(str_comp_nocase_num(a: str, b: prefix, num: prefixl) == 0)
2972 {
2973 return str + prefixl;
2974 }
2975 else
2976 {
2977 return 0;
2978 }
2979}
2980
2981const char *str_startswith(const char *str, const char *prefix)
2982{
2983 int prefixl = str_length(str: prefix);
2984 if(str_comp_num(a: str, b: prefix, num: prefixl) == 0)
2985 {
2986 return str + prefixl;
2987 }
2988 else
2989 {
2990 return 0;
2991 }
2992}
2993
2994const char *str_endswith_nocase(const char *str, const char *suffix)
2995{
2996 int strl = str_length(str);
2997 int suffixl = str_length(str: suffix);
2998 const char *strsuffix;
2999 if(strl < suffixl)
3000 {
3001 return 0;
3002 }
3003 strsuffix = str + strl - suffixl;
3004 if(str_comp_nocase(a: strsuffix, b: suffix) == 0)
3005 {
3006 return strsuffix;
3007 }
3008 else
3009 {
3010 return 0;
3011 }
3012}
3013
3014const char *str_endswith(const char *str, const char *suffix)
3015{
3016 int strl = str_length(str);
3017 int suffixl = str_length(str: suffix);
3018 const char *strsuffix;
3019 if(strl < suffixl)
3020 {
3021 return 0;
3022 }
3023 strsuffix = str + strl - suffixl;
3024 if(str_comp(a: strsuffix, b: suffix) == 0)
3025 {
3026 return strsuffix;
3027 }
3028 else
3029 {
3030 return 0;
3031 }
3032}
3033
3034static int min3(int a, int b, int c)
3035{
3036 int min = a;
3037 if(b < min)
3038 min = b;
3039 if(c < min)
3040 min = c;
3041 return min;
3042}
3043
3044int str_utf8_dist(const char *a, const char *b)
3045{
3046 int buf_len = 2 * (str_length(str: a) + 1 + str_length(str: b) + 1);
3047 int *buf = (int *)calloc(nmemb: buf_len, size: sizeof(*buf));
3048 int result = str_utf8_dist_buffer(a, b, buf, buf_len);
3049 free(ptr: buf);
3050 return result;
3051}
3052
3053static int str_to_utf32_unchecked(const char *str, int **out)
3054{
3055 int out_len = 0;
3056 while((**out = str_utf8_decode(ptr: &str)))
3057 {
3058 (*out)++;
3059 out_len++;
3060 }
3061 return out_len;
3062}
3063
3064int str_utf32_dist_buffer(const int *a, int a_len, const int *b, int b_len, int *buf, int buf_len)
3065{
3066 int i, j;
3067 dbg_assert(buf_len >= (a_len + 1) + (b_len + 1), "buffer too small");
3068 if(a_len > b_len)
3069 {
3070 int tmp1 = a_len;
3071 const int *tmp2 = a;
3072
3073 a_len = b_len;
3074 a = b;
3075
3076 b_len = tmp1;
3077 b = tmp2;
3078 }
3079#define B(i, j) buf[((j)&1) * (a_len + 1) + (i)]
3080 for(i = 0; i <= a_len; i++)
3081 {
3082 B(i, 0) = i;
3083 }
3084 for(j = 1; j <= b_len; j++)
3085 {
3086 B(0, j) = j;
3087 for(i = 1; i <= a_len; i++)
3088 {
3089 int subst = (a[i - 1] != b[j - 1]);
3090 B(i, j) = min3(
3091 B(i - 1, j) + 1,
3092 B(i, j - 1) + 1,
3093 B(i - 1, j - 1) + subst);
3094 }
3095 }
3096 return B(a_len, b_len);
3097#undef B
3098}
3099
3100int str_utf8_dist_buffer(const char *a_utf8, const char *b_utf8, int *buf, int buf_len)
3101{
3102 int a_utf8_len = str_length(str: a_utf8);
3103 int b_utf8_len = str_length(str: b_utf8);
3104 int *a, *b; // UTF-32
3105 int a_len, b_len; // UTF-32 length
3106 dbg_assert(buf_len >= 2 * (a_utf8_len + 1 + b_utf8_len + 1), "buffer too small");
3107 if(a_utf8_len > b_utf8_len)
3108 {
3109 const char *tmp2 = a_utf8;
3110 a_utf8 = b_utf8;
3111 b_utf8 = tmp2;
3112 }
3113 a = buf;
3114 a_len = str_to_utf32_unchecked(str: a_utf8, out: &buf);
3115 b = buf;
3116 b_len = str_to_utf32_unchecked(str: b_utf8, out: &buf);
3117 return str_utf32_dist_buffer(a, a_len, b, b_len, buf, buf_len: buf_len - b_len - a_len);
3118}
3119
3120const char *str_find_nocase(const char *haystack, const char *needle)
3121{
3122 while(*haystack) /* native implementation */
3123 {
3124 const char *a = haystack;
3125 const char *b = needle;
3126 while(*a && *b && tolower(c: (unsigned char)*a) == tolower(c: (unsigned char)*b))
3127 {
3128 a++;
3129 b++;
3130 }
3131 if(!(*b))
3132 return haystack;
3133 haystack++;
3134 }
3135
3136 return 0;
3137}
3138
3139const char *str_find(const char *haystack, const char *needle)
3140{
3141 while(*haystack) /* native implementation */
3142 {
3143 const char *a = haystack;
3144 const char *b = needle;
3145 while(*a && *b && *a == *b)
3146 {
3147 a++;
3148 b++;
3149 }
3150 if(!(*b))
3151 return haystack;
3152 haystack++;
3153 }
3154
3155 return 0;
3156}
3157
3158const char *str_rchr(const char *haystack, char needle)
3159{
3160 return strrchr(s: haystack, c: needle);
3161}
3162
3163int str_countchr(const char *haystack, char needle)
3164{
3165 int count = 0;
3166 while(*haystack)
3167 {
3168 if(*haystack == needle)
3169 count++;
3170 haystack++;
3171 }
3172 return count;
3173}
3174
3175void str_hex(char *dst, int dst_size, const void *data, int data_size)
3176{
3177 static const char hex[] = "0123456789ABCDEF";
3178 int data_index;
3179 int dst_index;
3180 for(data_index = 0, dst_index = 0; data_index < data_size && dst_index < dst_size - 3; data_index++)
3181 {
3182 dst[data_index * 3] = hex[((const unsigned char *)data)[data_index] >> 4];
3183 dst[data_index * 3 + 1] = hex[((const unsigned char *)data)[data_index] & 0xf];
3184 dst[data_index * 3 + 2] = ' ';
3185 dst_index += 3;
3186 }
3187 dst[dst_index] = '\0';
3188}
3189
3190void str_hex_cstyle(char *dst, int dst_size, const void *data, int data_size, int bytes_per_line)
3191{
3192 static const char hex[] = "0123456789ABCDEF";
3193 int data_index;
3194 int dst_index;
3195 int remaining_bytes_per_line = bytes_per_line;
3196 for(data_index = 0, dst_index = 0; data_index < data_size && dst_index < dst_size - 6; data_index++)
3197 {
3198 --remaining_bytes_per_line;
3199 dst[data_index * 6] = '0';
3200 dst[data_index * 6 + 1] = 'x';
3201 dst[data_index * 6 + 2] = hex[((const unsigned char *)data)[data_index] >> 4];
3202 dst[data_index * 6 + 3] = hex[((const unsigned char *)data)[data_index] & 0xf];
3203 dst[data_index * 6 + 4] = ',';
3204 if(remaining_bytes_per_line == 0)
3205 {
3206 dst[data_index * 6 + 5] = '\n';
3207 remaining_bytes_per_line = bytes_per_line;
3208 }
3209 else
3210 {
3211 dst[data_index * 6 + 5] = ' ';
3212 }
3213 dst_index += 6;
3214 }
3215 dst[dst_index] = '\0';
3216 // Remove trailing comma and space/newline
3217 if(dst_index >= 1)
3218 dst[dst_index - 1] = '\0';
3219 if(dst_index >= 2)
3220 dst[dst_index - 2] = '\0';
3221}
3222
3223static int hexval(char x)
3224{
3225 switch(x)
3226 {
3227 case '0': return 0;
3228 case '1': return 1;
3229 case '2': return 2;
3230 case '3': return 3;
3231 case '4': return 4;
3232 case '5': return 5;
3233 case '6': return 6;
3234 case '7': return 7;
3235 case '8': return 8;
3236 case '9': return 9;
3237 case 'a':
3238 case 'A': return 10;
3239 case 'b':
3240 case 'B': return 11;
3241 case 'c':
3242 case 'C': return 12;
3243 case 'd':
3244 case 'D': return 13;
3245 case 'e':
3246 case 'E': return 14;
3247 case 'f':
3248 case 'F': return 15;
3249 default: return -1;
3250 }
3251}
3252
3253static int byteval(const char *hex, unsigned char *dst)
3254{
3255 int v1 = hexval(x: hex[0]);
3256 int v2 = hexval(x: hex[1]);
3257
3258 if(v1 < 0 || v2 < 0)
3259 return 1;
3260
3261 *dst = v1 * 16 + v2;
3262 return 0;
3263}
3264
3265int str_hex_decode(void *dst, int dst_size, const char *src)
3266{
3267 unsigned char *cdst = (unsigned char *)dst;
3268 int slen = str_length(str: src);
3269 int len = slen / 2;
3270 int i;
3271 if(slen != dst_size * 2)
3272 return 2;
3273
3274 for(i = 0; i < len && dst_size; i++, dst_size--)
3275 {
3276 if(byteval(hex: src + i * 2, dst: cdst++))
3277 return 1;
3278 }
3279 return 0;
3280}
3281
3282void str_base64(char *dst, int dst_size, const void *data_raw, int data_size)
3283{
3284 static const char DIGITS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3285
3286 const unsigned char *data = (const unsigned char *)data_raw;
3287 unsigned value = 0;
3288 int num_bits = 0;
3289 int i = 0;
3290 int o = 0;
3291
3292 dst_size -= 1;
3293 dst[dst_size] = 0;
3294 while(true)
3295 {
3296 if(num_bits < 6 && i < data_size)
3297 {
3298 value = (value << 8) | data[i];
3299 num_bits += 8;
3300 i += 1;
3301 }
3302 if(o == dst_size)
3303 {
3304 return;
3305 }
3306 if(num_bits > 0)
3307 {
3308 unsigned padded;
3309 if(num_bits >= 6)
3310 {
3311 padded = (value >> (num_bits - 6)) & 0x3f;
3312 }
3313 else
3314 {
3315 padded = (value << (6 - num_bits)) & 0x3f;
3316 }
3317 dst[o] = DIGITS[padded];
3318 num_bits -= 6;
3319 o += 1;
3320 }
3321 else if(o % 4 != 0)
3322 {
3323 dst[o] = '=';
3324 o += 1;
3325 }
3326 else
3327 {
3328 dst[o] = 0;
3329 return;
3330 }
3331 }
3332}
3333
3334static int base64_digit_value(char digit)
3335{
3336 if('A' <= digit && digit <= 'Z')
3337 {
3338 return digit - 'A';
3339 }
3340 else if('a' <= digit && digit <= 'z')
3341 {
3342 return digit - 'a' + 26;
3343 }
3344 else if('0' <= digit && digit <= '9')
3345 {
3346 return digit - '0' + 52;
3347 }
3348 else if(digit == '+')
3349 {
3350 return 62;
3351 }
3352 else if(digit == '/')
3353 {
3354 return 63;
3355 }
3356 return -1;
3357}
3358
3359int str_base64_decode(void *dst_raw, int dst_size, const char *data)
3360{
3361 unsigned char *dst = (unsigned char *)dst_raw;
3362 int data_len = str_length(str: data);
3363
3364 int i;
3365 int o = 0;
3366
3367 if(data_len % 4 != 0)
3368 {
3369 return -3;
3370 }
3371 if(data_len / 4 * 3 > dst_size)
3372 {
3373 // Output buffer too small.
3374 return -2;
3375 }
3376 for(i = 0; i < data_len; i += 4)
3377 {
3378 int num_output_bytes = 3;
3379 char copy[4];
3380 int d[4];
3381 int value;
3382 int b;
3383 mem_copy(dest: copy, source: data + i, size: sizeof(copy));
3384 if(i == data_len - 4)
3385 {
3386 if(copy[3] == '=')
3387 {
3388 copy[3] = 'A';
3389 num_output_bytes = 2;
3390 if(copy[2] == '=')
3391 {
3392 copy[2] = 'A';
3393 num_output_bytes = 1;
3394 }
3395 }
3396 }
3397 d[0] = base64_digit_value(digit: copy[0]);
3398 d[1] = base64_digit_value(digit: copy[1]);
3399 d[2] = base64_digit_value(digit: copy[2]);
3400 d[3] = base64_digit_value(digit: copy[3]);
3401 if(d[0] == -1 || d[1] == -1 || d[2] == -1 || d[3] == -1)
3402 {
3403 // Invalid digit.
3404 return -1;
3405 }
3406 value = (d[0] << 18) | (d[1] << 12) | (d[2] << 6) | d[3];
3407 for(b = 0; b < 3; b++)
3408 {
3409 unsigned char byte_value = (value >> (16 - 8 * b)) & 0xff;
3410 if(b < num_output_bytes)
3411 {
3412 dst[o] = byte_value;
3413 o += 1;
3414 }
3415 else
3416 {
3417 if(byte_value != 0)
3418 {
3419 // Padding not zeroed.
3420 return -2;
3421 }
3422 }
3423 }
3424 }
3425 return o;
3426}
3427
3428#ifdef __GNUC__
3429#pragma GCC diagnostic push
3430#pragma GCC diagnostic ignored "-Wformat-nonliteral"
3431#endif
3432void str_timestamp_ex(time_t time_data, char *buffer, int buffer_size, const char *format)
3433{
3434 struct tm *time_info = time_localtime_threadlocal(time_data: &time_data);
3435 strftime(s: buffer, maxsize: buffer_size, format: format, tp: time_info);
3436 buffer[buffer_size - 1] = 0; /* assure null termination */
3437}
3438
3439void str_timestamp_format(char *buffer, int buffer_size, const char *format)
3440{
3441 time_t time_data;
3442 time(timer: &time_data);
3443 str_timestamp_ex(time_data, buffer, buffer_size, format);
3444}
3445
3446void str_timestamp(char *buffer, int buffer_size)
3447{
3448 str_timestamp_format(buffer, buffer_size, FORMAT_NOSPACE);
3449}
3450
3451bool timestamp_from_str(const char *string, const char *format, time_t *timestamp)
3452{
3453 std::tm tm{};
3454 std::istringstream ss(string);
3455 ss >> std::get_time(tmb: &tm, fmt: format);
3456 if(ss.fail() || !ss.eof())
3457 return false;
3458
3459 time_t result = mktime(tp: &tm);
3460 if(result < 0)
3461 return false;
3462
3463 *timestamp = result;
3464 return true;
3465}
3466#ifdef __GNUC__
3467#pragma GCC diagnostic pop
3468#endif
3469
3470int str_time(int64_t centisecs, int format, char *buffer, int buffer_size)
3471{
3472 const int sec = 100;
3473 const int min = 60 * sec;
3474 const int hour = 60 * min;
3475 const int day = 24 * hour;
3476
3477 if(buffer_size <= 0)
3478 return -1;
3479
3480 if(centisecs < 0)
3481 centisecs = 0;
3482
3483 buffer[0] = 0;
3484
3485 switch(format)
3486 {
3487 case TIME_DAYS:
3488 if(centisecs >= day)
3489 return str_format(buffer, buffer_size, "%" PRId64 "d %02" PRId64 ":%02" PRId64 ":%02" PRId64, centisecs / day,
3490 (centisecs % day) / hour, (centisecs % hour) / min, (centisecs % min) / sec);
3491 [[fallthrough]];
3492 case TIME_HOURS:
3493 if(centisecs >= hour)
3494 return str_format(buffer, buffer_size, "%02" PRId64 ":%02" PRId64 ":%02" PRId64, centisecs / hour,
3495 (centisecs % hour) / min, (centisecs % min) / sec);
3496 [[fallthrough]];
3497 case TIME_MINS:
3498 return str_format(buffer, buffer_size, "%02" PRId64 ":%02" PRId64, centisecs / min,
3499 (centisecs % min) / sec);
3500 case TIME_HOURS_CENTISECS:
3501 if(centisecs >= hour)
3502 return str_format(buffer, buffer_size, "%02" PRId64 ":%02" PRId64 ":%02" PRId64 ".%02" PRId64, centisecs / hour,
3503 (centisecs % hour) / min, (centisecs % min) / sec, centisecs % sec);
3504 [[fallthrough]];
3505 case TIME_MINS_CENTISECS:
3506 if(centisecs >= min)
3507 return str_format(buffer, buffer_size, "%02" PRId64 ":%02" PRId64 ".%02" PRId64, centisecs / min,
3508 (centisecs % min) / sec, centisecs % sec);
3509 [[fallthrough]];
3510 case TIME_SECS_CENTISECS:
3511 return str_format(buffer, buffer_size, "%02" PRId64 ".%02" PRId64, (centisecs % min) / sec, centisecs % sec);
3512 }
3513
3514 return -1;
3515}
3516
3517int str_time_float(float secs, int format, char *buffer, int buffer_size)
3518{
3519 return str_time(centisecs: llroundf(x: secs * 1000) / 10, format, buffer, buffer_size);
3520}
3521
3522void str_escape(char **dst, const char *src, const char *end)
3523{
3524 while(*src && *dst + 1 < end)
3525 {
3526 if(*src == '"' || *src == '\\') // escape \ and "
3527 {
3528 if(*dst + 2 < end)
3529 *(*dst)++ = '\\';
3530 else
3531 break;
3532 }
3533 *(*dst)++ = *src++;
3534 }
3535 **dst = 0;
3536}
3537
3538void net_stats(NETSTATS *stats_inout)
3539{
3540 *stats_inout = network_stats;
3541}
3542
3543int str_isspace(char c)
3544{
3545 return c == ' ' || c == '\n' || c == '\r' || c == '\t';
3546}
3547
3548char str_uppercase(char c)
3549{
3550 if(c >= 'a' && c <= 'z')
3551 return 'A' + (c - 'a');
3552 return c;
3553}
3554
3555bool str_isnum(char c)
3556{
3557 return c >= '0' && c <= '9';
3558}
3559
3560int str_isallnum(const char *str)
3561{
3562 while(*str)
3563 {
3564 if(!str_isnum(c: *str))
3565 return 0;
3566 str++;
3567 }
3568 return 1;
3569}
3570
3571int str_isallnum_hex(const char *str)
3572{
3573 while(*str)
3574 {
3575 if(!str_isnum(c: *str) && !(*str >= 'a' && *str <= 'f') && !(*str >= 'A' && *str <= 'F'))
3576 return 0;
3577 str++;
3578 }
3579 return 1;
3580}
3581
3582int str_toint(const char *str)
3583{
3584 return str_toint_base(str, base: 10);
3585}
3586
3587bool str_toint(const char *str, int *out)
3588{
3589 // returns true if conversion was successful
3590 char *end;
3591 int value = strtol(nptr: str, endptr: &end, base: 10);
3592 if(*end != '\0')
3593 return false;
3594 if(out != nullptr)
3595 *out = value;
3596 return true;
3597}
3598
3599int str_toint_base(const char *str, int base)
3600{
3601 return strtol(nptr: str, endptr: nullptr, base: base);
3602}
3603
3604unsigned long str_toulong_base(const char *str, int base)
3605{
3606 return strtoul(nptr: str, endptr: nullptr, base: base);
3607}
3608
3609int64_t str_toint64_base(const char *str, int base)
3610{
3611 return strtoll(nptr: str, endptr: nullptr, base: base);
3612}
3613
3614float str_tofloat(const char *str)
3615{
3616 return strtod(nptr: str, endptr: nullptr);
3617}
3618
3619bool str_tofloat(const char *str, float *out)
3620{
3621 // returns true if conversion was successful
3622 char *end;
3623 float value = strtod(nptr: str, endptr: &end);
3624 if(*end != '\0')
3625 return false;
3626 if(out != nullptr)
3627 *out = value;
3628 return true;
3629}
3630
3631int str_utf8_comp_nocase(const char *a, const char *b)
3632{
3633 int code_a;
3634 int code_b;
3635
3636 while(*a && *b)
3637 {
3638 code_a = str_utf8_tolower(code: str_utf8_decode(ptr: &a));
3639 code_b = str_utf8_tolower(code: str_utf8_decode(ptr: &b));
3640
3641 if(code_a != code_b)
3642 return code_a - code_b;
3643 }
3644 return (unsigned char)*a - (unsigned char)*b;
3645}
3646
3647int str_utf8_comp_nocase_num(const char *a, const char *b, int num)
3648{
3649 int code_a;
3650 int code_b;
3651 const char *old_a = a;
3652
3653 if(num <= 0)
3654 return 0;
3655
3656 while(*a && *b)
3657 {
3658 code_a = str_utf8_tolower(code: str_utf8_decode(ptr: &a));
3659 code_b = str_utf8_tolower(code: str_utf8_decode(ptr: &b));
3660
3661 if(code_a != code_b)
3662 return code_a - code_b;
3663
3664 if(a - old_a >= num)
3665 return 0;
3666 }
3667
3668 return (unsigned char)*a - (unsigned char)*b;
3669}
3670
3671const char *str_utf8_find_nocase(const char *haystack, const char *needle, const char **end)
3672{
3673 while(*haystack) /* native implementation */
3674 {
3675 const char *a = haystack;
3676 const char *b = needle;
3677 const char *a_next = a;
3678 const char *b_next = b;
3679 while(*a && *b && str_utf8_tolower(code: str_utf8_decode(ptr: &a_next)) == str_utf8_tolower(code: str_utf8_decode(ptr: &b_next)))
3680 {
3681 a = a_next;
3682 b = b_next;
3683 }
3684 if(!(*b))
3685 {
3686 if(end != nullptr)
3687 *end = a_next;
3688 return haystack;
3689 }
3690 str_utf8_decode(ptr: &haystack);
3691 }
3692
3693 if(end != nullptr)
3694 *end = nullptr;
3695 return nullptr;
3696}
3697
3698int str_utf8_isspace(int code)
3699{
3700 return code <= 0x0020 || code == 0x0085 || code == 0x00A0 || code == 0x034F ||
3701 code == 0x115F || code == 0x1160 || code == 0x1680 || code == 0x180E ||
3702 (code >= 0x2000 && code <= 0x200F) || (code >= 0x2028 && code <= 0x202F) ||
3703 (code >= 0x205F && code <= 0x2064) || (code >= 0x206A && code <= 0x206F) ||
3704 code == 0x2800 || code == 0x3000 || code == 0x3164 ||
3705 (code >= 0xFE00 && code <= 0xFE0F) || code == 0xFEFF || code == 0xFFA0 ||
3706 (code >= 0xFFF9 && code <= 0xFFFC);
3707}
3708
3709const char *str_utf8_skip_whitespaces(const char *str)
3710{
3711 const char *str_old;
3712 int code;
3713
3714 while(*str)
3715 {
3716 str_old = str;
3717 code = str_utf8_decode(ptr: &str);
3718
3719 // check if unicode is not empty
3720 if(!str_utf8_isspace(code))
3721 {
3722 return str_old;
3723 }
3724 }
3725
3726 return str;
3727}
3728
3729void str_utf8_trim_right(char *param)
3730{
3731 const char *str = param;
3732 char *end = 0;
3733 while(*str)
3734 {
3735 char *str_old = (char *)str;
3736 int code = str_utf8_decode(ptr: &str);
3737
3738 // check if unicode is not empty
3739 if(!str_utf8_isspace(code))
3740 {
3741 end = 0;
3742 }
3743 else if(!end)
3744 {
3745 end = str_old;
3746 }
3747 }
3748 if(end)
3749 {
3750 *end = 0;
3751 }
3752}
3753
3754int str_utf8_isstart(char c)
3755{
3756 if((c & 0xC0) == 0x80) /* 10xxxxxx */
3757 return 0;
3758 return 1;
3759}
3760
3761int str_utf8_rewind(const char *str, int cursor)
3762{
3763 while(cursor)
3764 {
3765 cursor--;
3766 if(str_utf8_isstart(c: *(str + cursor)))
3767 break;
3768 }
3769 return cursor;
3770}
3771
3772int str_utf8_fix_truncation(char *str)
3773{
3774 int len = str_length(str);
3775 if(len > 0)
3776 {
3777 int last_char_index = str_utf8_rewind(str, cursor: len);
3778 const char *last_char = str + last_char_index;
3779 // Fix truncated UTF-8.
3780 if(str_utf8_decode(ptr: &last_char) == -1)
3781 {
3782 str[last_char_index] = 0;
3783 return last_char_index;
3784 }
3785 }
3786 return len;
3787}
3788
3789int str_utf8_forward(const char *str, int cursor)
3790{
3791 const char *ptr = str + cursor;
3792 if(str_utf8_decode(ptr: &ptr) == 0)
3793 {
3794 return cursor;
3795 }
3796 return ptr - str;
3797}
3798
3799int str_utf8_encode(char *ptr, int chr)
3800{
3801 /* encode */
3802 if(chr <= 0x7F)
3803 {
3804 ptr[0] = (char)chr;
3805 return 1;
3806 }
3807 else if(chr <= 0x7FF)
3808 {
3809 ptr[0] = 0xC0 | ((chr >> 6) & 0x1F);
3810 ptr[1] = 0x80 | (chr & 0x3F);
3811 return 2;
3812 }
3813 else if(chr <= 0xFFFF)
3814 {
3815 ptr[0] = 0xE0 | ((chr >> 12) & 0x0F);
3816 ptr[1] = 0x80 | ((chr >> 6) & 0x3F);
3817 ptr[2] = 0x80 | (chr & 0x3F);
3818 return 3;
3819 }
3820 else if(chr <= 0x10FFFF)
3821 {
3822 ptr[0] = 0xF0 | ((chr >> 18) & 0x07);
3823 ptr[1] = 0x80 | ((chr >> 12) & 0x3F);
3824 ptr[2] = 0x80 | ((chr >> 6) & 0x3F);
3825 ptr[3] = 0x80 | (chr & 0x3F);
3826 return 4;
3827 }
3828
3829 return 0;
3830}
3831
3832static unsigned char str_byte_next(const char **ptr)
3833{
3834 unsigned char byte_value = **ptr;
3835 (*ptr)++;
3836 return byte_value;
3837}
3838
3839static void str_byte_rewind(const char **ptr)
3840{
3841 (*ptr)--;
3842}
3843
3844int str_utf8_decode(const char **ptr)
3845{
3846 // As per https://encoding.spec.whatwg.org/#utf-8-decoder.
3847 unsigned char utf8_lower_boundary = 0x80;
3848 unsigned char utf8_upper_boundary = 0xBF;
3849 int utf8_code_point = 0;
3850 int utf8_bytes_seen = 0;
3851 int utf8_bytes_needed = 0;
3852 while(true)
3853 {
3854 unsigned char byte_value = str_byte_next(ptr);
3855 if(utf8_bytes_needed == 0)
3856 {
3857 if(byte_value <= 0x7F)
3858 {
3859 return byte_value;
3860 }
3861 else if(0xC2 <= byte_value && byte_value <= 0xDF)
3862 {
3863 utf8_bytes_needed = 1;
3864 utf8_code_point = byte_value - 0xC0;
3865 }
3866 else if(0xE0 <= byte_value && byte_value <= 0xEF)
3867 {
3868 if(byte_value == 0xE0)
3869 utf8_lower_boundary = 0xA0;
3870 if(byte_value == 0xED)
3871 utf8_upper_boundary = 0x9F;
3872 utf8_bytes_needed = 2;
3873 utf8_code_point = byte_value - 0xE0;
3874 }
3875 else if(0xF0 <= byte_value && byte_value <= 0xF4)
3876 {
3877 if(byte_value == 0xF0)
3878 utf8_lower_boundary = 0x90;
3879 if(byte_value == 0xF4)
3880 utf8_upper_boundary = 0x8F;
3881 utf8_bytes_needed = 3;
3882 utf8_code_point = byte_value - 0xF0;
3883 }
3884 else
3885 {
3886 return -1; // Error.
3887 }
3888 utf8_code_point = utf8_code_point << (6 * utf8_bytes_needed);
3889 continue;
3890 }
3891 if(!(utf8_lower_boundary <= byte_value && byte_value <= utf8_upper_boundary))
3892 {
3893 // Resetting variables not necessary, will be done when
3894 // the function is called again.
3895 str_byte_rewind(ptr);
3896 return -1;
3897 }
3898 utf8_lower_boundary = 0x80;
3899 utf8_upper_boundary = 0xBF;
3900 utf8_bytes_seen += 1;
3901 utf8_code_point = utf8_code_point + ((byte_value - 0x80) << (6 * (utf8_bytes_needed - utf8_bytes_seen)));
3902 if(utf8_bytes_seen != utf8_bytes_needed)
3903 {
3904 continue;
3905 }
3906 // Resetting variables not necessary, see above.
3907 return utf8_code_point;
3908 }
3909}
3910
3911int str_utf8_check(const char *str)
3912{
3913 int codepoint;
3914 while((codepoint = str_utf8_decode(ptr: &str)))
3915 {
3916 if(codepoint == -1)
3917 {
3918 return 0;
3919 }
3920 }
3921 return 1;
3922}
3923
3924void str_utf8_stats(const char *str, size_t max_size, size_t max_count, size_t *size, size_t *count)
3925{
3926 const char *cursor = str;
3927 *size = 0;
3928 *count = 0;
3929 while(*size < max_size && *count < max_count)
3930 {
3931 if(str_utf8_decode(ptr: &cursor) == 0)
3932 {
3933 break;
3934 }
3935 if((size_t)(cursor - str) >= max_size)
3936 {
3937 break;
3938 }
3939 *size = cursor - str;
3940 ++(*count);
3941 }
3942}
3943
3944size_t str_utf8_offset_bytes_to_chars(const char *str, size_t byte_offset)
3945{
3946 size_t char_offset = 0;
3947 size_t current_offset = 0;
3948 while(current_offset < byte_offset)
3949 {
3950 const size_t prev_byte_offset = current_offset;
3951 current_offset = str_utf8_forward(str, cursor: current_offset);
3952 if(current_offset == prev_byte_offset)
3953 break;
3954 char_offset++;
3955 }
3956 return char_offset;
3957}
3958
3959size_t str_utf8_offset_chars_to_bytes(const char *str, size_t char_offset)
3960{
3961 size_t byte_offset = 0;
3962 for(size_t i = 0; i < char_offset; i++)
3963 {
3964 const size_t prev_byte_offset = byte_offset;
3965 byte_offset = str_utf8_forward(str, cursor: byte_offset);
3966 if(byte_offset == prev_byte_offset)
3967 break;
3968 }
3969 return byte_offset;
3970}
3971
3972unsigned str_quickhash(const char *str)
3973{
3974 unsigned hash = 5381;
3975 for(; *str; str++)
3976 hash = ((hash << 5) + hash) + (*str); /* hash * 33 + c */
3977 return hash;
3978}
3979
3980static const char *str_token_get(const char *str, const char *delim, int *length)
3981{
3982 size_t len = strspn(s: str, accept: delim);
3983 if(len > 1)
3984 str++;
3985 else
3986 str += len;
3987 if(!*str)
3988 return NULL;
3989
3990 *length = strcspn(s: str, reject: delim);
3991 return str;
3992}
3993
3994int str_in_list(const char *list, const char *delim, const char *needle)
3995{
3996 const char *tok = list;
3997 int len = 0, notfound = 1, needlelen = str_length(str: needle);
3998
3999 while(notfound && (tok = str_token_get(str: tok, delim, length: &len)))
4000 {
4001 notfound = needlelen != len || str_comp_num(a: tok, b: needle, num: len);
4002 tok = tok + len;
4003 }
4004
4005 return !notfound;
4006}
4007
4008const char *str_next_token(const char *str, const char *delim, char *buffer, int buffer_size)
4009{
4010 int len = 0;
4011 const char *tok = str_token_get(str, delim, length: &len);
4012 if(len < 0 || tok == NULL)
4013 {
4014 buffer[0] = '\0';
4015 return NULL;
4016 }
4017
4018 len = buffer_size > len ? len : buffer_size - 1;
4019 mem_copy(dest: buffer, source: tok, size: len);
4020 buffer[len] = '\0';
4021
4022 return tok + len;
4023}
4024
4025static_assert(sizeof(unsigned) == 4, "unsigned must be 4 bytes in size");
4026static_assert(sizeof(unsigned) == sizeof(int), "unsigned and int must have the same size");
4027
4028unsigned bytes_be_to_uint(const unsigned char *bytes)
4029{
4030 return ((bytes[0] & 0xffu) << 24u) | ((bytes[1] & 0xffu) << 16u) | ((bytes[2] & 0xffu) << 8u) | (bytes[3] & 0xffu);
4031}
4032
4033void uint_to_bytes_be(unsigned char *bytes, unsigned value)
4034{
4035 bytes[0] = (value >> 24u) & 0xffu;
4036 bytes[1] = (value >> 16u) & 0xffu;
4037 bytes[2] = (value >> 8u) & 0xffu;
4038 bytes[3] = value & 0xffu;
4039}
4040
4041int pid()
4042{
4043#if defined(CONF_FAMILY_WINDOWS)
4044 return _getpid();
4045#else
4046 return getpid();
4047#endif
4048}
4049
4050void cmdline_fix(int *argc, const char ***argv)
4051{
4052#if defined(CONF_FAMILY_WINDOWS)
4053 int wide_argc = 0;
4054 WCHAR **wide_argv = CommandLineToArgvW(GetCommandLineW(), &wide_argc);
4055 dbg_assert(wide_argv != NULL, "CommandLineToArgvW failure");
4056 dbg_assert(wide_argc > 0, "Invalid argc value");
4057
4058 int total_size = 0;
4059
4060 for(int i = 0; i < wide_argc; i++)
4061 {
4062 int size = WideCharToMultiByte(CP_UTF8, 0, wide_argv[i], -1, NULL, 0, NULL, NULL);
4063 dbg_assert(size != 0, "WideCharToMultiByte failure");
4064 total_size += size;
4065 }
4066
4067 char **new_argv = (char **)malloc((wide_argc + 1) * sizeof(*new_argv));
4068 new_argv[0] = (char *)malloc(total_size);
4069 mem_zero(new_argv[0], total_size);
4070
4071 int remaining_size = total_size;
4072 for(int i = 0; i < wide_argc; i++)
4073 {
4074 int size = WideCharToMultiByte(CP_UTF8, 0, wide_argv[i], -1, new_argv[i], remaining_size, NULL, NULL);
4075 dbg_assert(size != 0, "WideCharToMultiByte failure");
4076
4077 remaining_size -= size;
4078 new_argv[i + 1] = new_argv[i] + size;
4079 }
4080
4081 LocalFree(wide_argv);
4082 new_argv[wide_argc] = 0;
4083 *argc = wide_argc;
4084 *argv = (const char **)new_argv;
4085#endif
4086}
4087
4088void cmdline_free(int argc, const char **argv)
4089{
4090#if defined(CONF_FAMILY_WINDOWS)
4091 free((void *)*argv);
4092 free((char **)argv);
4093#endif
4094}
4095
4096PROCESS shell_execute(const char *file, EShellExecuteWindowState window_state)
4097{
4098#if defined(CONF_FAMILY_WINDOWS)
4099 const std::wstring wide_file = windows_utf8_to_wide(file);
4100
4101 SHELLEXECUTEINFOW info;
4102 mem_zero(&info, sizeof(SHELLEXECUTEINFOW));
4103 info.cbSize = sizeof(SHELLEXECUTEINFOW);
4104 info.lpVerb = L"open";
4105 info.lpFile = wide_file.c_str();
4106 switch(window_state)
4107 {
4108 case EShellExecuteWindowState::FOREGROUND:
4109 info.nShow = SW_SHOW;
4110 break;
4111 case EShellExecuteWindowState::BACKGROUND:
4112 info.nShow = SW_SHOWMINNOACTIVE;
4113 break;
4114 default:
4115 dbg_assert(false, "window_state invalid");
4116 dbg_break();
4117 }
4118 info.fMask = SEE_MASK_NOCLOSEPROCESS;
4119 // Save and restore the FPU control word because ShellExecute might change it
4120 fenv_t floating_point_environment;
4121 int fegetenv_result = fegetenv(&floating_point_environment);
4122 ShellExecuteExW(&info);
4123 if(fegetenv_result == 0)
4124 fesetenv(&floating_point_environment);
4125 return info.hProcess;
4126#elif defined(CONF_FAMILY_UNIX)
4127 char *argv[2];
4128 pid_t pid;
4129 argv[0] = (char *)file;
4130 argv[1] = NULL;
4131 pid = fork();
4132 if(pid == -1)
4133 {
4134 return 0;
4135 }
4136 if(pid == 0)
4137 {
4138 execvp(file: file, argv: argv);
4139 _exit(status: 1);
4140 }
4141 return pid;
4142#endif
4143}
4144
4145int kill_process(PROCESS process)
4146{
4147#if defined(CONF_FAMILY_WINDOWS)
4148 BOOL success = TerminateProcess(process, 0);
4149 BOOL is_alive = is_process_alive(process);
4150 if(success || !is_alive)
4151 {
4152 CloseHandle(process);
4153 return true;
4154 }
4155 return false;
4156#elif defined(CONF_FAMILY_UNIX)
4157 if(!is_process_alive(process))
4158 return true;
4159 int status;
4160 kill(pid: process, SIGTERM);
4161 return waitpid(pid: process, stat_loc: &status, options: 0) != -1;
4162#endif
4163}
4164
4165bool is_process_alive(PROCESS process)
4166{
4167 if(process == INVALID_PROCESS)
4168 return false;
4169#if defined(CONF_FAMILY_WINDOWS)
4170 DWORD exit_code;
4171 GetExitCodeProcess(process, &exit_code);
4172 return exit_code == STILL_ACTIVE;
4173#else
4174 return waitpid(pid: process, stat_loc: nullptr, WNOHANG) == 0;
4175#endif
4176}
4177
4178#if !defined(CONF_PLATFORM_ANDROID)
4179int open_link(const char *link)
4180{
4181#if defined(CONF_FAMILY_WINDOWS)
4182 const std::wstring wide_link = windows_utf8_to_wide(link);
4183
4184 SHELLEXECUTEINFOW info;
4185 mem_zero(&info, sizeof(SHELLEXECUTEINFOW));
4186 info.cbSize = sizeof(SHELLEXECUTEINFOW);
4187 info.lpVerb = NULL; // NULL to use the default verb, as "open" may not be available
4188 info.lpFile = wide_link.c_str();
4189 info.nShow = SW_SHOWNORMAL;
4190 // The SEE_MASK_NOASYNC flag ensures that the ShellExecuteEx function
4191 // finishes its DDE conversation before it returns, so it's not necessary
4192 // to pump messages in the calling thread.
4193 // The SEE_MASK_FLAG_NO_UI flag suppresses error messages that would pop up
4194 // when the link cannot be opened, e.g. when a folder does not exist.
4195 // The SEE_MASK_ASYNCOK flag is not used. It would allow the call to
4196 // ShellExecuteEx to return earlier, but it also prevents us from doing
4197 // our own error handling, as the function would always return TRUE.
4198 info.fMask = SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI;
4199 // Save and restore the FPU control word because ShellExecute might change it
4200 fenv_t floating_point_environment;
4201 int fegetenv_result = fegetenv(&floating_point_environment);
4202 BOOL success = ShellExecuteExW(&info);
4203 if(fegetenv_result == 0)
4204 fesetenv(&floating_point_environment);
4205 return success;
4206#elif defined(CONF_PLATFORM_LINUX)
4207 const int pid = fork();
4208 if(pid == 0)
4209 execlp(file: "xdg-open", arg: "xdg-open", link, nullptr);
4210 return pid > 0;
4211#elif defined(CONF_FAMILY_UNIX)
4212 const int pid = fork();
4213 if(pid == 0)
4214 execlp("open", "open", link, nullptr);
4215 return pid > 0;
4216#endif
4217}
4218
4219int open_file(const char *path)
4220{
4221#if defined(CONF_PLATFORM_MACOS)
4222 return open_link(path);
4223#else
4224 // Create a file link so the path can contain forward and
4225 // backward slashes. But the file link must be absolute.
4226 char buf[512];
4227 char workingDir[IO_MAX_PATH_LENGTH];
4228 if(fs_is_relative_path(path))
4229 {
4230 if(!fs_getcwd(buffer: workingDir, buffer_size: sizeof(workingDir)))
4231 return 0;
4232 str_append(dst&: workingDir, src: "/");
4233 }
4234 else
4235 workingDir[0] = '\0';
4236 str_format(buffer: buf, buffer_size: sizeof(buf), format: "file://%s%s", workingDir, path);
4237 return open_link(link: buf);
4238#endif
4239}
4240#endif // !defined(CONF_PLATFORM_ANDROID)
4241
4242struct SECURE_RANDOM_DATA
4243{
4244 int initialized;
4245#if defined(CONF_FAMILY_WINDOWS)
4246 HCRYPTPROV provider;
4247#else
4248 IOHANDLE urandom;
4249#endif
4250};
4251
4252static struct SECURE_RANDOM_DATA secure_random_data = {.initialized: 0};
4253
4254int secure_random_init()
4255{
4256 if(secure_random_data.initialized)
4257 {
4258 return 0;
4259 }
4260#if defined(CONF_FAMILY_WINDOWS)
4261 if(CryptAcquireContext(&secure_random_data.provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
4262 {
4263 secure_random_data.initialized = 1;
4264 return 0;
4265 }
4266 else
4267 {
4268 return 1;
4269 }
4270#else
4271 secure_random_data.urandom = io_open(filename: "/dev/urandom", flags: IOFLAG_READ);
4272 if(secure_random_data.urandom)
4273 {
4274 secure_random_data.initialized = 1;
4275 return 0;
4276 }
4277 else
4278 {
4279 return 1;
4280 }
4281#endif
4282}
4283
4284int secure_random_uninit()
4285{
4286 if(!secure_random_data.initialized)
4287 {
4288 return 0;
4289 }
4290#if defined(CONF_FAMILY_WINDOWS)
4291 if(CryptReleaseContext(secure_random_data.provider, 0))
4292 {
4293 secure_random_data.initialized = 0;
4294 return 0;
4295 }
4296 else
4297 {
4298 return 1;
4299 }
4300#else
4301 if(!io_close(io: secure_random_data.urandom))
4302 {
4303 secure_random_data.initialized = 0;
4304 return 0;
4305 }
4306 else
4307 {
4308 return 1;
4309 }
4310#endif
4311}
4312
4313void generate_password(char *buffer, unsigned length, const unsigned short *random, unsigned random_length)
4314{
4315 static const char VALUES[] = "ABCDEFGHKLMNPRSTUVWXYZabcdefghjkmnopqt23456789";
4316 static const size_t NUM_VALUES = sizeof(VALUES) - 1; // Disregard the '\0'.
4317 unsigned i;
4318 dbg_assert(length >= random_length * 2 + 1, "too small buffer");
4319 dbg_assert(NUM_VALUES * NUM_VALUES >= 2048, "need at least 2048 possibilities for 2-character sequences");
4320
4321 buffer[random_length * 2] = 0;
4322
4323 for(i = 0; i < random_length; i++)
4324 {
4325 unsigned short random_number = random[i] % 2048;
4326 buffer[2 * i + 0] = VALUES[random_number / NUM_VALUES];
4327 buffer[2 * i + 1] = VALUES[random_number % NUM_VALUES];
4328 }
4329}
4330
4331#define MAX_PASSWORD_LENGTH 128
4332
4333void secure_random_password(char *buffer, unsigned length, unsigned pw_length)
4334{
4335 unsigned short random[MAX_PASSWORD_LENGTH / 2];
4336 // With 6 characters, we get a password entropy of log(2048) * 6/2 = 33bit.
4337 dbg_assert(length >= pw_length + 1, "too small buffer");
4338 dbg_assert(pw_length >= 6, "too small password length");
4339 dbg_assert(pw_length % 2 == 0, "need an even password length");
4340 dbg_assert(pw_length <= MAX_PASSWORD_LENGTH, "too large password length");
4341
4342 secure_random_fill(bytes: random, length: pw_length);
4343
4344 generate_password(buffer, length, random, random_length: pw_length / 2);
4345}
4346
4347#undef MAX_PASSWORD_LENGTH
4348
4349void secure_random_fill(void *bytes, unsigned length)
4350{
4351 if(!secure_random_data.initialized)
4352 {
4353 dbg_msg(sys: "secure", fmt: "called secure_random_fill before secure_random_init");
4354 dbg_break();
4355 }
4356#if defined(CONF_FAMILY_WINDOWS)
4357 if(!CryptGenRandom(secure_random_data.provider, length, (unsigned char *)bytes))
4358 {
4359 const DWORD LastError = GetLastError();
4360 const std::string ErrorMsg = windows_format_system_message(LastError);
4361 dbg_msg("secure", "CryptGenRandom failed: %ld %s", LastError, ErrorMsg.c_str());
4362 dbg_break();
4363 }
4364#else
4365 if(length != io_read(io: secure_random_data.urandom, buffer: bytes, size: length))
4366 {
4367 dbg_msg(sys: "secure", fmt: "io_read returned with a short read");
4368 dbg_break();
4369 }
4370#endif
4371}
4372
4373int secure_rand()
4374{
4375 unsigned int i;
4376 secure_random_fill(bytes: &i, length: sizeof(i));
4377 return (int)(i % RAND_MAX);
4378}
4379
4380// From https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2.
4381static unsigned int find_next_power_of_two_minus_one(unsigned int n)
4382{
4383 n--;
4384 n |= n >> 1;
4385 n |= n >> 2;
4386 n |= n >> 4;
4387 n |= n >> 4;
4388 n |= n >> 16;
4389 return n;
4390}
4391
4392int secure_rand_below(int below)
4393{
4394 unsigned int mask = find_next_power_of_two_minus_one(n: below);
4395 dbg_assert(below > 0, "below must be positive");
4396 while(true)
4397 {
4398 unsigned int n;
4399 secure_random_fill(bytes: &n, length: sizeof(n));
4400 n &= mask;
4401 if((int)n < below)
4402 {
4403 return n;
4404 }
4405 }
4406}
4407
4408bool os_version_str(char *version, size_t length)
4409{
4410#if defined(CONF_FAMILY_WINDOWS)
4411 const WCHAR *module_path = L"kernel32.dll";
4412 DWORD handle;
4413 DWORD size = GetFileVersionInfoSizeW(module_path, &handle);
4414 if(!size)
4415 {
4416 return false;
4417 }
4418 void *data = malloc(size);
4419 if(!GetFileVersionInfoW(module_path, handle, size, data))
4420 {
4421 free(data);
4422 return false;
4423 }
4424 VS_FIXEDFILEINFO *fileinfo;
4425 UINT unused;
4426 if(!VerQueryValueW(data, L"\\", (void **)&fileinfo, &unused))
4427 {
4428 free(data);
4429 return false;
4430 }
4431 str_format(version, length, "Windows %hu.%hu.%hu.%hu",
4432 HIWORD(fileinfo->dwProductVersionMS),
4433 LOWORD(fileinfo->dwProductVersionMS),
4434 HIWORD(fileinfo->dwProductVersionLS),
4435 LOWORD(fileinfo->dwProductVersionLS));
4436 free(data);
4437 return true;
4438#else
4439 struct utsname u;
4440 if(uname(name: &u))
4441 {
4442 return false;
4443 }
4444 char extra[128];
4445 extra[0] = 0;
4446
4447 do
4448 {
4449 IOHANDLE os_release = io_open(filename: "/etc/os-release", flags: IOFLAG_READ);
4450 char buf[4096];
4451 int read;
4452 int offset;
4453 char *newline;
4454 if(!os_release)
4455 {
4456 break;
4457 }
4458 read = io_read(io: os_release, buffer: buf, size: sizeof(buf) - 1);
4459 io_close(io: os_release);
4460 buf[read] = 0;
4461 if(str_startswith(str: buf, prefix: "PRETTY_NAME="))
4462 {
4463 offset = 0;
4464 }
4465 else
4466 {
4467 const char *found = str_find(haystack: buf, needle: "\nPRETTY_NAME=");
4468 if(!found)
4469 {
4470 break;
4471 }
4472 offset = found - buf + 1;
4473 }
4474 newline = (char *)str_find(haystack: buf + offset, needle: "\n");
4475 if(newline)
4476 {
4477 *newline = 0;
4478 }
4479 str_format(buffer: extra, buffer_size: sizeof(extra), format: "; %s", buf + offset + 12);
4480 } while(false);
4481
4482 str_format(buffer: version, buffer_size: length, format: "%s %s (%s, %s)%s", u.sysname, u.release, u.machine, u.version, extra);
4483 return true;
4484#endif
4485}
4486
4487void os_locale_str(char *locale, size_t length)
4488{
4489#if defined(CONF_FAMILY_WINDOWS)
4490 wchar_t wide_buffer[LOCALE_NAME_MAX_LENGTH];
4491 dbg_assert(GetUserDefaultLocaleName(wide_buffer, std::size(wide_buffer)) > 0, "GetUserDefaultLocaleName failure");
4492
4493 const std::optional<std::string> buffer = windows_wide_to_utf8(wide_buffer);
4494 dbg_assert(buffer.has_value(), "GetUserDefaultLocaleName returned invalid UTF-16");
4495 str_copy(locale, buffer.value().c_str(), length);
4496#elif defined(CONF_PLATFORM_MACOS)
4497 CFLocaleRef locale_ref = CFLocaleCopyCurrent();
4498 CFStringRef locale_identifier_ref = static_cast<CFStringRef>(CFLocaleGetValue(locale_ref, kCFLocaleIdentifier));
4499
4500 // Count number of UTF16 codepoints, +1 for zero-termination.
4501 // Assume maximum possible length for encoding as UTF-8.
4502 CFIndex locale_identifier_size = (UTF8_BYTE_LENGTH * CFStringGetLength(locale_identifier_ref) + 1) * sizeof(char);
4503 char *locale_identifier = (char *)malloc(locale_identifier_size);
4504 dbg_assert(CFStringGetCString(locale_identifier_ref, locale_identifier, locale_identifier_size, kCFStringEncodingUTF8), "CFStringGetCString failure");
4505
4506 str_copy(locale, locale_identifier, length);
4507
4508 free(locale_identifier);
4509 CFRelease(locale_ref);
4510#else
4511 static const char *ENV_VARIABLES[] = {
4512 "LC_ALL",
4513 "LC_MESSAGES",
4514 "LANG",
4515 };
4516
4517 locale[0] = '\0';
4518 for(const char *env_variable : ENV_VARIABLES)
4519 {
4520 const char *env_value = getenv(name: env_variable);
4521 if(env_value)
4522 {
4523 str_copy(dst: locale, src: env_value, dst_size: length);
4524 break;
4525 }
4526 }
4527#endif
4528
4529 // Ensure RFC 3066 format:
4530 // - use hyphens instead of underscores
4531 // - truncate locale string after first non-standard letter
4532 for(int i = 0; i < str_length(str: locale); ++i)
4533 {
4534 if(locale[i] == '_')
4535 {
4536 locale[i] = '-';
4537 }
4538 else if(locale[i] != '-' && !(locale[i] >= 'a' && locale[i] <= 'z') && !(locale[i] >= 'A' && locale[i] <= 'Z') && !(str_isnum(c: locale[i])))
4539 {
4540 locale[i] = '\0';
4541 break;
4542 }
4543 }
4544
4545 // Use default if we could not determine the locale,
4546 // i.e. if only the C or POSIX locale is available.
4547 if(locale[0] == '\0' || str_comp(a: locale, b: "C") == 0 || str_comp(a: locale, b: "POSIX") == 0)
4548 str_copy(dst: locale, src: "en-US", dst_size: length);
4549}
4550
4551#if defined(CONF_EXCEPTION_HANDLING)
4552#if defined(CONF_FAMILY_WINDOWS)
4553static HMODULE exception_handling_module = nullptr;
4554#endif
4555
4556void init_exception_handler()
4557{
4558#if defined(CONF_FAMILY_WINDOWS)
4559 const char *module_name = "exchndl.dll";
4560 exception_handling_module = LoadLibraryA(module_name);
4561 if(exception_handling_module == nullptr)
4562 {
4563 const DWORD LastError = GetLastError();
4564 const std::string ErrorMsg = windows_format_system_message(LastError);
4565 dbg_msg("exception_handling", "failed to load exception handling library '%s' (error %ld %s)", module_name, LastError, ErrorMsg.c_str());
4566 }
4567#else
4568#error exception handling not implemented
4569#endif
4570}
4571
4572void set_exception_handler_log_file(const char *log_file_path)
4573{
4574#if defined(CONF_FAMILY_WINDOWS)
4575 if(exception_handling_module != nullptr)
4576 {
4577 const std::wstring wide_log_file_path = windows_utf8_to_wide(log_file_path);
4578 // Intentional
4579#ifdef __MINGW32__
4580#pragma GCC diagnostic push
4581#pragma GCC diagnostic ignored "-Wcast-function-type"
4582#endif
4583 const char *function_name = "ExcHndlSetLogFileNameW";
4584 auto exception_log_file_path_func = (BOOL(APIENTRY *)(const WCHAR *))(GetProcAddress(exception_handling_module, function_name));
4585#ifdef __MINGW32__
4586#pragma GCC diagnostic pop
4587#endif
4588 if(exception_log_file_path_func == nullptr)
4589 {
4590 const DWORD LastError = GetLastError();
4591 const std::string ErrorMsg = windows_format_system_message(LastError);
4592 dbg_msg("exception_handling", "could not find function '%s' in exception handling library (error %ld %s)", function_name, LastError, ErrorMsg.c_str());
4593 }
4594 else
4595 exception_log_file_path_func(wide_log_file_path.c_str());
4596 }
4597#else
4598#error exception handling not implemented
4599#endif
4600}
4601#endif
4602
4603std::chrono::nanoseconds time_get_nanoseconds()
4604{
4605 return std::chrono::nanoseconds(time_get_impl());
4606}
4607
4608int net_socket_read_wait(NETSOCKET sock, std::chrono::nanoseconds nanoseconds)
4609{
4610 using namespace std::chrono_literals;
4611 return ::net_socket_read_wait(sock, time: (nanoseconds / std::chrono::nanoseconds(1us).count()).count());
4612}
4613
4614#if defined(CONF_FAMILY_WINDOWS)
4615std::wstring windows_utf8_to_wide(const char *str)
4616{
4617 const int orig_length = str_length(str);
4618 if(orig_length == 0)
4619 return L"";
4620 const int size_needed = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str, orig_length, nullptr, 0);
4621 dbg_assert(size_needed > 0, "Invalid UTF-8 passed to windows_utf8_to_wide");
4622 std::wstring wide_string(size_needed, L'\0');
4623 dbg_assert(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str, orig_length, wide_string.data(), size_needed) == size_needed, "MultiByteToWideChar failure");
4624 return wide_string;
4625}
4626
4627std::optional<std::string> windows_wide_to_utf8(const wchar_t *wide_str)
4628{
4629 const int orig_length = wcslen(wide_str);
4630 if(orig_length == 0)
4631 return "";
4632 const int size_needed = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, wide_str, orig_length, nullptr, 0, nullptr, nullptr);
4633 if(size_needed == 0)
4634 return {};
4635 std::string string(size_needed, '\0');
4636 dbg_assert(WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, wide_str, orig_length, string.data(), size_needed, nullptr, nullptr) == size_needed, "WideCharToMultiByte failure");
4637 return string;
4638}
4639
4640// See https://learn.microsoft.com/en-us/windows/win32/learnwin32/initializing-the-com-library
4641CWindowsComLifecycle::CWindowsComLifecycle(bool HasWindow)
4642{
4643 HRESULT result = CoInitializeEx(NULL, (HasWindow ? COINIT_APARTMENTTHREADED : COINIT_MULTITHREADED) | COINIT_DISABLE_OLE1DDE);
4644 dbg_assert(result != S_FALSE, "COM library already initialized on this thread");
4645 dbg_assert(result == S_OK, "COM library initialization failed");
4646}
4647CWindowsComLifecycle::~CWindowsComLifecycle()
4648{
4649 CoUninitialize();
4650}
4651
4652static void windows_print_error(const char *system, const char *prefix, HRESULT error)
4653{
4654 const std::string message = windows_format_system_message(error);
4655 dbg_msg(system, "%s: %s", prefix, message.c_str());
4656}
4657
4658static std::wstring filename_from_path(const std::wstring &path)
4659{
4660 const size_t pos = path.find_last_of(L"/\\");
4661 return pos == std::wstring::npos ? path : path.substr(pos + 1);
4662}
4663
4664bool shell_register_protocol(const char *protocol_name, const char *executable, bool *updated)
4665{
4666 const std::wstring protocol_name_wide = windows_utf8_to_wide(protocol_name);
4667 const std::wstring executable_wide = windows_utf8_to_wide(executable);
4668
4669 // Open registry key for protocol associations of the current user
4670 HKEY handle_subkey_classes;
4671 const LRESULT result_subkey_classes = RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Classes", 0, KEY_ALL_ACCESS, &handle_subkey_classes);
4672 if(result_subkey_classes != ERROR_SUCCESS)
4673 {
4674 windows_print_error("shell_register_protocol", "Error opening registry key", result_subkey_classes);
4675 return false;
4676 }
4677
4678 // Create the protocol key
4679 HKEY handle_subkey_protocol;
4680 const LRESULT result_subkey_protocol = RegCreateKeyExW(handle_subkey_classes, protocol_name_wide.c_str(), 0, NULL, 0, KEY_ALL_ACCESS, NULL, &handle_subkey_protocol, NULL);
4681 RegCloseKey(handle_subkey_classes);
4682 if(result_subkey_protocol != ERROR_SUCCESS)
4683 {
4684 windows_print_error("shell_register_protocol", "Error creating registry key", result_subkey_protocol);
4685 return false;
4686 }
4687
4688 // Set the default value for the key, which specifies the name of the display name of the protocol
4689 const std::wstring value_protocol = L"URL:" + protocol_name_wide + L" Protocol";
4690 const LRESULT result_value_protocol = RegSetValueExW(handle_subkey_protocol, L"", 0, REG_SZ, (BYTE *)value_protocol.c_str(), (value_protocol.length() + 1) * sizeof(wchar_t));
4691 if(result_value_protocol != ERROR_SUCCESS)
4692 {
4693 windows_print_error("shell_register_protocol", "Error setting registry value", result_value_protocol);
4694 RegCloseKey(handle_subkey_protocol);
4695 return false;
4696 }
4697
4698 // Set the "URL Protocol" value, to specify that this key describes a URL protocol
4699 const LRESULT result_value_empty = RegSetValueEx(handle_subkey_protocol, L"URL Protocol", 0, REG_SZ, (BYTE *)L"", sizeof(wchar_t));
4700 if(result_value_empty != ERROR_SUCCESS)
4701 {
4702 windows_print_error("shell_register_protocol", "Error setting registry value", result_value_empty);
4703 RegCloseKey(handle_subkey_protocol);
4704 return false;
4705 }
4706
4707 // Create the "DefaultIcon" subkey
4708 HKEY handle_subkey_icon;
4709 const LRESULT result_subkey_icon = RegCreateKeyExW(handle_subkey_protocol, L"DefaultIcon", 0, NULL, 0, KEY_ALL_ACCESS, NULL, &handle_subkey_icon, NULL);
4710 if(result_subkey_icon != ERROR_SUCCESS)
4711 {
4712 windows_print_error("shell_register_protocol", "Error creating registry key", result_subkey_icon);
4713 RegCloseKey(handle_subkey_protocol);
4714 return false;
4715 }
4716
4717 // Set the default value for the key, which specifies the icon associated with the protocol
4718 const std::wstring value_icon = L"\"" + executable_wide + L"\",0";
4719 const LRESULT result_value_icon = RegSetValueExW(handle_subkey_icon, L"", 0, REG_SZ, (BYTE *)value_icon.c_str(), (value_icon.length() + 1) * sizeof(wchar_t));
4720 RegCloseKey(handle_subkey_icon);
4721 if(result_value_icon != ERROR_SUCCESS)
4722 {
4723 windows_print_error("shell_register_protocol", "Error setting registry value", result_value_icon);
4724 RegCloseKey(handle_subkey_protocol);
4725 return false;
4726 }
4727
4728 // Create the "shell\open\command" subkeys
4729 HKEY handle_subkey_shell_open_command;
4730 const LRESULT result_subkey_shell_open_command = RegCreateKeyExW(handle_subkey_protocol, L"shell\\open\\command", 0, NULL, 0, KEY_ALL_ACCESS, NULL, &handle_subkey_shell_open_command, NULL);
4731 RegCloseKey(handle_subkey_protocol);
4732 if(result_subkey_shell_open_command != ERROR_SUCCESS)
4733 {
4734 windows_print_error("shell_register_protocol", "Error creating registry key", result_subkey_shell_open_command);
4735 return false;
4736 }
4737
4738 // Get the previous default value for the key, so we can determine if it changed
4739 wchar_t old_value_executable[MAX_PATH + 16];
4740 DWORD old_size_executable = sizeof(old_value_executable);
4741 const LRESULT result_old_value_executable = RegGetValueW(handle_subkey_shell_open_command, NULL, L"", RRF_RT_REG_SZ, NULL, (BYTE *)old_value_executable, &old_size_executable);
4742 const std::wstring value_executable = L"\"" + executable_wide + L"\" \"%1\"";
4743 if(result_old_value_executable != ERROR_SUCCESS || wcscmp(old_value_executable, value_executable.c_str()) != 0)
4744 {
4745 // Set the default value for the key, which specifies the executable command associated with the protocol
4746 const LRESULT result_value_executable = RegSetValueExW(handle_subkey_shell_open_command, L"", 0, REG_SZ, (BYTE *)value_executable.c_str(), (value_executable.length() + 1) * sizeof(wchar_t));
4747 RegCloseKey(handle_subkey_shell_open_command);
4748 if(result_value_executable != ERROR_SUCCESS)
4749 {
4750 windows_print_error("shell_register_protocol", "Error setting registry value", result_value_executable);
4751 return false;
4752 }
4753
4754 *updated = true;
4755 }
4756 else
4757 {
4758 RegCloseKey(handle_subkey_shell_open_command);
4759 }
4760
4761 return true;
4762}
4763
4764bool shell_register_extension(const char *extension, const char *description, const char *executable_name, const char *executable, bool *updated)
4765{
4766 const std::wstring extension_wide = windows_utf8_to_wide(extension);
4767 const std::wstring executable_name_wide = windows_utf8_to_wide(executable_name);
4768 const std::wstring description_wide = executable_name_wide + L" " + windows_utf8_to_wide(description);
4769 const std::wstring program_id_wide = executable_name_wide + extension_wide;
4770 const std::wstring executable_wide = windows_utf8_to_wide(executable);
4771
4772 // Open registry key for file associations of the current user
4773 HKEY handle_subkey_classes;
4774 const LRESULT result_subkey_classes = RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Classes", 0, KEY_ALL_ACCESS, &handle_subkey_classes);
4775 if(result_subkey_classes != ERROR_SUCCESS)
4776 {
4777 windows_print_error("shell_register_extension", "Error opening registry key", result_subkey_classes);
4778 return false;
4779 }
4780
4781 // Create the program ID key
4782 HKEY handle_subkey_program_id;
4783 const LRESULT result_subkey_program_id = RegCreateKeyExW(handle_subkey_classes, program_id_wide.c_str(), 0, NULL, 0, KEY_ALL_ACCESS, NULL, &handle_subkey_program_id, NULL);
4784 if(result_subkey_program_id != ERROR_SUCCESS)
4785 {
4786 windows_print_error("shell_register_extension", "Error creating registry key", result_subkey_program_id);
4787 RegCloseKey(handle_subkey_classes);
4788 return false;
4789 }
4790
4791 // Set the default value for the key, which specifies the file type description for legacy applications
4792 const LRESULT result_description_default = RegSetValueExW(handle_subkey_program_id, L"", 0, REG_SZ, (BYTE *)description_wide.c_str(), (description_wide.length() + 1) * sizeof(wchar_t));
4793 if(result_description_default != ERROR_SUCCESS)
4794 {
4795 windows_print_error("shell_register_extension", "Error setting registry value", result_description_default);
4796 RegCloseKey(handle_subkey_program_id);
4797 RegCloseKey(handle_subkey_classes);
4798 return false;
4799 }
4800
4801 // Set the "FriendlyTypeName" value, which specifies the file type description for modern applications
4802 const LRESULT result_description_friendly = RegSetValueExW(handle_subkey_program_id, L"FriendlyTypeName", 0, REG_SZ, (BYTE *)description_wide.c_str(), (description_wide.length() + 1) * sizeof(wchar_t));
4803 if(result_description_friendly != ERROR_SUCCESS)
4804 {
4805 windows_print_error("shell_register_extension", "Error setting registry value", result_description_friendly);
4806 RegCloseKey(handle_subkey_program_id);
4807 RegCloseKey(handle_subkey_classes);
4808 return false;
4809 }
4810
4811 // Create the "DefaultIcon" subkey
4812 HKEY handle_subkey_icon;
4813 const LRESULT result_subkey_icon = RegCreateKeyExW(handle_subkey_program_id, L"DefaultIcon", 0, NULL, 0, KEY_ALL_ACCESS, NULL, &handle_subkey_icon, NULL);
4814 if(result_subkey_icon != ERROR_SUCCESS)
4815 {
4816 windows_print_error("register_protocol", "Error creating registry key", result_subkey_icon);
4817 RegCloseKey(handle_subkey_program_id);
4818 RegCloseKey(handle_subkey_classes);
4819 return false;
4820 }
4821
4822 // Set the default value for the key, which specifies the icon associated with the program ID
4823 const std::wstring value_icon = L"\"" + executable_wide + L"\",0";
4824 const LRESULT result_value_icon = RegSetValueExW(handle_subkey_icon, L"", 0, REG_SZ, (BYTE *)value_icon.c_str(), (value_icon.length() + 1) * sizeof(wchar_t));
4825 RegCloseKey(handle_subkey_icon);
4826 if(result_value_icon != ERROR_SUCCESS)
4827 {
4828 windows_print_error("register_protocol", "Error setting registry value", result_value_icon);
4829 RegCloseKey(handle_subkey_program_id);
4830 RegCloseKey(handle_subkey_classes);
4831 return false;
4832 }
4833
4834 // Create the "shell\open\command" subkeys
4835 HKEY handle_subkey_shell_open_command;
4836 const LRESULT result_subkey_shell_open_command = RegCreateKeyExW(handle_subkey_program_id, L"shell\\open\\command", 0, NULL, 0, KEY_ALL_ACCESS, NULL, &handle_subkey_shell_open_command, NULL);
4837 RegCloseKey(handle_subkey_program_id);
4838 if(result_subkey_shell_open_command != ERROR_SUCCESS)
4839 {
4840 windows_print_error("shell_register_extension", "Error creating registry key", result_subkey_shell_open_command);
4841 RegCloseKey(handle_subkey_classes);
4842 return false;
4843 }
4844
4845 // Get the previous default value for the key, so we can determine if it changed
4846 wchar_t old_value_executable[MAX_PATH + 16];
4847 DWORD old_size_executable = sizeof(old_value_executable);
4848 const LRESULT result_old_value_executable = RegGetValueW(handle_subkey_shell_open_command, NULL, L"", RRF_RT_REG_SZ, NULL, (BYTE *)old_value_executable, &old_size_executable);
4849 const std::wstring value_executable = L"\"" + executable_wide + L"\" \"%1\"";
4850 if(result_old_value_executable != ERROR_SUCCESS || wcscmp(old_value_executable, value_executable.c_str()) != 0)
4851 {
4852 // Set the default value for the key, which specifies the executable command associated with the application
4853 const LRESULT result_value_executable = RegSetValueExW(handle_subkey_shell_open_command, L"", 0, REG_SZ, (BYTE *)value_executable.c_str(), (value_executable.length() + 1) * sizeof(wchar_t));
4854 RegCloseKey(handle_subkey_shell_open_command);
4855 if(result_value_executable != ERROR_SUCCESS)
4856 {
4857 windows_print_error("shell_register_extension", "Error setting registry value", result_value_executable);
4858 RegCloseKey(handle_subkey_classes);
4859 return false;
4860 }
4861
4862 *updated = true;
4863 }
4864
4865 // Create the file extension key
4866 HKEY handle_subkey_extension;
4867 const LRESULT result_subkey_extension = RegCreateKeyExW(handle_subkey_classes, extension_wide.c_str(), 0, NULL, 0, KEY_ALL_ACCESS, NULL, &handle_subkey_extension, NULL);
4868 RegCloseKey(handle_subkey_classes);
4869 if(result_subkey_extension != ERROR_SUCCESS)
4870 {
4871 windows_print_error("shell_register_extension", "Error creating registry key", result_subkey_extension);
4872 return false;
4873 }
4874
4875 // Get the previous default value for the key, so we can determine if it changed
4876 wchar_t old_value_application[128];
4877 DWORD old_size_application = sizeof(old_value_application);
4878 const LRESULT result_old_value_application = RegGetValueW(handle_subkey_extension, NULL, L"", RRF_RT_REG_SZ, NULL, (BYTE *)old_value_application, &old_size_application);
4879 if(result_old_value_application != ERROR_SUCCESS || wcscmp(old_value_application, program_id_wide.c_str()) != 0)
4880 {
4881 // Set the default value for the key, which associates the file extension with the program ID
4882 const LRESULT result_value_application = RegSetValueExW(handle_subkey_extension, L"", 0, REG_SZ, (BYTE *)program_id_wide.c_str(), (program_id_wide.length() + 1) * sizeof(wchar_t));
4883 RegCloseKey(handle_subkey_extension);
4884 if(result_value_application != ERROR_SUCCESS)
4885 {
4886 windows_print_error("shell_register_extension", "Error setting registry value", result_value_application);
4887 return false;
4888 }
4889
4890 *updated = true;
4891 }
4892 else
4893 {
4894 RegCloseKey(handle_subkey_extension);
4895 }
4896
4897 return true;
4898}
4899
4900bool shell_register_application(const char *name, const char *executable, bool *updated)
4901{
4902 const std::wstring name_wide = windows_utf8_to_wide(name);
4903 const std::wstring executable_filename = filename_from_path(windows_utf8_to_wide(executable));
4904
4905 // Open registry key for application registrations
4906 HKEY handle_subkey_applications;
4907 const LRESULT result_subkey_applications = RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Classes\\Applications", 0, KEY_ALL_ACCESS, &handle_subkey_applications);
4908 if(result_subkey_applications != ERROR_SUCCESS)
4909 {
4910 windows_print_error("shell_register_application", "Error opening registry key", result_subkey_applications);
4911 return false;
4912 }
4913
4914 // Create the program key
4915 HKEY handle_subkey_program;
4916 const LRESULT result_subkey_program = RegCreateKeyExW(handle_subkey_applications, executable_filename.c_str(), 0, NULL, 0, KEY_ALL_ACCESS, NULL, &handle_subkey_program, NULL);
4917 RegCloseKey(handle_subkey_applications);
4918 if(result_subkey_program != ERROR_SUCCESS)
4919 {
4920 windows_print_error("shell_register_application", "Error creating registry key", result_subkey_program);
4921 return false;
4922 }
4923
4924 // Get the previous default value for the key, so we can determine if it changed
4925 wchar_t old_value_executable[MAX_PATH];
4926 DWORD old_size_executable = sizeof(old_value_executable);
4927 const LRESULT result_old_value_executable = RegGetValueW(handle_subkey_program, NULL, L"FriendlyAppName", RRF_RT_REG_SZ, NULL, (BYTE *)old_value_executable, &old_size_executable);
4928 if(result_old_value_executable != ERROR_SUCCESS || wcscmp(old_value_executable, name_wide.c_str()) != 0)
4929 {
4930 // Set the "FriendlyAppName" value, which specifies the displayed name of the application
4931 const LRESULT result_program_name = RegSetValueExW(handle_subkey_program, L"FriendlyAppName", 0, REG_SZ, (BYTE *)name_wide.c_str(), (name_wide.length() + 1) * sizeof(wchar_t));
4932 RegCloseKey(handle_subkey_program);
4933 if(result_program_name != ERROR_SUCCESS)
4934 {
4935 windows_print_error("shell_register_application", "Error setting registry value", result_program_name);
4936 return false;
4937 }
4938
4939 *updated = true;
4940 }
4941 else
4942 {
4943 RegCloseKey(handle_subkey_program);
4944 }
4945
4946 return true;
4947}
4948
4949bool shell_unregister_class(const char *shell_class, bool *updated)
4950{
4951 const std::wstring class_wide = windows_utf8_to_wide(shell_class);
4952
4953 // Open registry key for protocol and file associations of the current user
4954 HKEY handle_subkey_classes;
4955 const LRESULT result_subkey_classes = RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Classes", 0, KEY_ALL_ACCESS, &handle_subkey_classes);
4956 if(result_subkey_classes != ERROR_SUCCESS)
4957 {
4958 windows_print_error("shell_unregister_class", "Error opening registry key", result_subkey_classes);
4959 return false;
4960 }
4961
4962 // Delete the registry keys for the shell class (protocol or program ID)
4963 LRESULT result_delete = RegDeleteTreeW(handle_subkey_classes, class_wide.c_str());
4964 RegCloseKey(handle_subkey_classes);
4965 if(result_delete == ERROR_SUCCESS)
4966 {
4967 *updated = true;
4968 }
4969 else if(result_delete != ERROR_FILE_NOT_FOUND)
4970 {
4971 windows_print_error("shell_unregister_class", "Error deleting registry key", result_delete);
4972 return false;
4973 }
4974
4975 return true;
4976}
4977
4978bool shell_unregister_application(const char *executable, bool *updated)
4979{
4980 const std::wstring executable_filename = filename_from_path(windows_utf8_to_wide(executable));
4981
4982 // Open registry key for application registrations
4983 HKEY handle_subkey_applications;
4984 const LRESULT result_subkey_applications = RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Classes\\Applications", 0, KEY_ALL_ACCESS, &handle_subkey_applications);
4985 if(result_subkey_applications != ERROR_SUCCESS)
4986 {
4987 windows_print_error("shell_unregister_application", "Error opening registry key", result_subkey_applications);
4988 return false;
4989 }
4990
4991 // Delete the registry keys for the application description
4992 LRESULT result_delete = RegDeleteTreeW(handle_subkey_applications, executable_filename.c_str());
4993 RegCloseKey(handle_subkey_applications);
4994 if(result_delete == ERROR_SUCCESS)
4995 {
4996 *updated = true;
4997 }
4998 else if(result_delete != ERROR_FILE_NOT_FOUND)
4999 {
5000 windows_print_error("shell_unregister_application", "Error deleting registry key", result_delete);
5001 return false;
5002 }
5003
5004 return true;
5005}
5006
5007void shell_update()
5008{
5009 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
5010}
5011#endif
5012
5013size_t std::hash<NETADDR>::operator()(const NETADDR &Addr) const noexcept
5014{
5015 return std::hash<std::string_view>{}(std::string_view((const char *)&Addr, sizeof(Addr)));
5016}
5017