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