tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

dlg.c (21039B)


      1 // Copyright (c) 2019 nyorain
      2 // Distributed under the Boost Software License, Version 1.0.
      3 // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt
      4 
      5 #define _XOPEN_SOURCE 600
      6 #define _POSIX_C_SOURCE 200809L
      7 #define _WIN32_WINNT 0x0600
      8 
      9 // Needed on windows so that we can use sprintf without warning.
     10 #define _CRT_SECURE_NO_WARNINGS
     11 
     12 #include <dlg/output.h>
     13 #include <dlg/dlg.h>
     14 #include <wchar.h>
     15 #include <time.h>
     16 #include <stdio.h>
     17 #include <stdlib.h>
     18 #include <string.h>
     19 
     20 const char* const dlg_reset_sequence = "\033[0m";
     21 const struct dlg_style dlg_default_output_styles[] = {
     22 {dlg_text_style_italic, dlg_color_green, dlg_color_none},
     23 {dlg_text_style_dim, dlg_color_gray, dlg_color_none},
     24 {dlg_text_style_none, dlg_color_cyan, dlg_color_none},
     25 {dlg_text_style_none, dlg_color_yellow, dlg_color_none},
     26 {dlg_text_style_none, dlg_color_red, dlg_color_none},
     27 {dlg_text_style_bold, dlg_color_red, dlg_color_none}
     28 };
     29 
     30 static void* xalloc(size_t size) {
     31 void* ret = calloc(size, 1);
     32 if(!ret) fprintf(stderr, "dlg: calloc returned NULL, probably crashing (size: %zu)\n", size);
     33 return ret;
     34 }
     35 
     36 static void* xrealloc(void* ptr, size_t size) {
     37 void* ret = realloc(ptr, size);
     38 if(!ret) fprintf(stderr, "dlg: realloc returned NULL, probably crashing (size: %zu)\n", size);
     39 return ret;
     40 }
     41 
     42 struct dlg_tag_func_pair {
     43 const char* tag;
     44 const char* func;
     45 };
     46 
     47 struct dlg_data {
     48 const char** tags; // vec
     49 struct dlg_tag_func_pair* pairs; // vec
     50 char* buffer;
     51 size_t buffer_size;
     52 };
     53 
     54 static dlg_handler g_handler = dlg_default_output;
     55 static void* g_data = NULL;
     56 
     57 static void dlg_free_data(void* data);
     58 static struct dlg_data* dlg_create_data(void);
     59 
     60 // platform-specific
     61 #if defined(__unix__) || defined(__unix) || defined(__linux__) || defined(__APPLE__) || defined(__MACH__)
     62 #define DLG_OS_UNIX
     63 #include <unistd.h>
     64 #include <pthread.h>
     65 #include <sys/time.h>
     66 
     67 static pthread_key_t dlg_data_key;
     68 
     69 static void dlg_main_cleanup(void) {
     70 	void* data = pthread_getspecific(dlg_data_key);
     71 	if(data) {
     72 		dlg_free_data(data);
     73 		pthread_setspecific(dlg_data_key, NULL);
     74 	}
     75 }
     76 
     77 static void init_data_key(void) {
     78 	pthread_key_create(&dlg_data_key, dlg_free_data);
     79 	atexit(dlg_main_cleanup);
     80 }
     81 
     82 static struct dlg_data* dlg_data(void) {
     83 	static pthread_once_t key_once = PTHREAD_ONCE_INIT;
     84 	pthread_once(&key_once, init_data_key);
     85 
     86 	void* data = pthread_getspecific(dlg_data_key);
     87 	if(!data) {
     88 		data = dlg_create_data();
     89 		pthread_setspecific(dlg_data_key, data);
     90 	}
     91 
     92 	return (struct dlg_data*) data;
     93 }
     94 
     95 static void lock_file(FILE* file) {
     96 	flockfile(file);
     97 }
     98 
     99 static void unlock_file(FILE* file) {
    100 	funlockfile(file);
    101 }
    102 
    103 bool dlg_is_tty(FILE* stream) {
    104 	return isatty(fileno(stream));
    105 }
    106 
    107 static unsigned get_msecs(void) {
    108 	struct timeval tv;
    109 	gettimeofday(&tv, NULL);
    110 	return tv.tv_usec;
    111 }
    112 
    113 // platform switch -- end unix
    114 #elif defined(WIN32) || defined(_WIN32) || defined(_WIN64)
    115 #define DLG_OS_WIN
    116 #define WIN32_LEAN_AND_MEAN
    117 #define DEFINE_CONSOLEV2_PROPERTIES
    118 #include <windows.h>
    119 #include <io.h>
    120 
    121 // thanks for nothing, microsoft
    122 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
    123 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
    124 #endif
    125 
    126 // the max buffer size we will convert on the stack
    127 #define DLG_MAX_STACK_BUF_SIZE 1024
    128 
    129 static void WINAPI dlg_fls_destructor(void* data) {
    130 	dlg_free_data(data);
    131 }
    132 
    133 // TODO: error handling
    134 static BOOL CALLBACK dlg_init_fls(PINIT_ONCE io, void* param, void** lpContext) {
    135 	(void) io;
    136 	(void) param;
    137 	**((DWORD**) lpContext) = FlsAlloc(dlg_fls_destructor);
    138 	return true;
    139 }
    140 
    141 static struct dlg_data* dlg_data(void) {
    142 	static INIT_ONCE init_once = INIT_ONCE_STATIC_INIT;
    143 	static DWORD fls = 0;
    144 	void* flsp = (void*) &fls;
    145 	InitOnceExecuteOnce(&init_once, dlg_init_fls, NULL, &flsp);
    146 	void* data = FlsGetValue(fls);
    147 	if(!data) {
    148 		data = dlg_create_data();
    149 		FlsSetValue(fls, data);
    150 	}
    151 
    152 	return (struct dlg_data*) data;
    153 }
    154 
    155 static void lock_file(FILE* file) {
    156 	_lock_file(file);
    157 }
    158 
    159 static void unlock_file(FILE* file) {
    160 	_unlock_file(file);
    161 }
    162 
    163 bool dlg_is_tty(FILE* stream) {
    164 	return _isatty(_fileno(stream));
    165 }
    166 
    167 #ifdef DLG_WIN_CONSOLE
    168 static bool init_ansi_console(void) {
    169 	HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
    170 	HANDLE err = GetStdHandle(STD_ERROR_HANDLE);
    171 	if(out == INVALID_HANDLE_VALUE || err == INVALID_HANDLE_VALUE)
    172 		return false;
    173 
    174 	DWORD outMode, errMode;
    175 	if(!GetConsoleMode(out, &outMode) || !GetConsoleMode(err, &errMode))
    176 	   return false;
    177 
    178 	outMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
    179 	errMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
    180 	if(!SetConsoleMode(out, outMode) || !SetConsoleMode(out, errMode))
    181 		return false;
    182 
    183 	return true;
    184 }
    185 
    186 static bool win_write_heap(void* handle, int needed, const char* format, va_list args) {
    187 	char* buf1 = xalloc(3 * needed + 3 + (needed % 2));
    188 	wchar_t* buf2 = (wchar_t*) (buf1 + needed + 1 + (needed % 2));
    189 	vsnprintf(buf1, needed + 1, format, args);
    190     needed = MultiByteToWideChar(CP_UTF8, 0, buf1, needed, buf2, needed + 1);
    191 	bool ret = (needed != 0 && WriteConsoleW(handle, buf2, needed, NULL, NULL) != 0);
    192 	free(buf1);
    193 	return ret;
    194 }
    195 
    196 static bool win_write_stack(void* handle, int needed, const char* format, va_list args) {
    197 	char buf1[DLG_MAX_STACK_BUF_SIZE];
    198 	wchar_t buf2[DLG_MAX_STACK_BUF_SIZE];
    199 	vsnprintf(buf1, needed + 1, format, args);
    200     needed = MultiByteToWideChar(CP_UTF8, 0, buf1, needed, buf2, needed + 1);
    201 	return (needed != 0 && WriteConsoleW(handle, buf2, needed, NULL, NULL) != 0);
    202 }
    203 #endif // DLG_WIN_CONSOLE
    204 
    205 static unsigned get_msecs() {
    206 	SYSTEMTIME st;
    207 	GetSystemTime(&st);
    208 	return st.wMilliseconds;
    209 }
    210 
    211 #else // platform switch -- end windows
    212 #error Cannot determine platform (needed for color and utf-8 and stuff)
    213 #endif
    214 
    215 // general
    216 void dlg_escape_sequence(struct dlg_style style, char buf[12]) {
    217 int nums[3];
    218 unsigned int count = 0;
    219 
    220 if(style.fg != dlg_color_none) {
    221 	nums[count++] = style.fg + 30;
    222 }
    223 
    224 if(style.bg != dlg_color_none) {
    225 	nums[count++] = style.fg + 40;
    226 }
    227 
    228 if(style.style != dlg_text_style_none) {
    229 	nums[count++] = style.style;
    230 }
    231 
    232 switch(count) {
    233 	case 1: snprintf(buf, 12, "\033[%dm", nums[0]); break;
    234 	case 2: snprintf(buf, 12, "\033[%d;%dm", nums[0], nums[1]); break;
    235 	case 3: snprintf(buf, 12, "\033[%d;%d;%dm", nums[0], nums[1], nums[2]); break;
    236 	default: buf[0] = '\0'; break;
    237 }
    238 }
    239 
    240 int dlg_vfprintf(FILE* stream, const char* format, va_list args) {
    241 #if defined(DLG_OS_WIN) && defined(DLG_WIN_CONSOLE)
    242 void* handle = NULL;
    243 if(stream == stdout) {
    244 	handle = GetStdHandle(STD_OUTPUT_HANDLE);
    245 } else if(stream == stderr) {
    246 	handle = GetStdHandle(STD_ERROR_HANDLE);
    247 }
    248 
    249 if(handle) {
    250 	va_list args_copy;
    251 	va_copy(args_copy, args);
    252 	int needed = vsnprintf(NULL, 0, format, args_copy);
    253 	va_end(args_copy);
    254 
    255 	if(needed < 0) {
    256 		return needed;
    257 	}
    258 
    259 	// We don't allocate too much on the stack
    260 	// but we also don't want to call alloc every logging call
    261 	// or use another cached buffer
    262 	if(needed >= DLG_MAX_STACK_BUF_SIZE) {
    263 		if(win_write_heap(handle, needed, format, args)) {
    264 			return needed;
    265 		}
    266 	} else {
    267 		if(win_write_stack(handle, needed, format, args)) {
    268 			return needed;
    269 		}
    270 	}
    271 }
    272 #endif
    273 
    274 return vfprintf(stream, format, args);
    275 }
    276 
    277 int dlg_fprintf(FILE* stream, const char* format, ...) {
    278 va_list args;
    279 va_start(args, format);
    280 int ret = dlg_vfprintf(stream, format, args);
    281 va_end(args);
    282 return ret;
    283 }
    284 
    285 int dlg_styled_fprintf(FILE* stream, struct dlg_style style, const char* format, ...) {
    286 char buf[12];
    287 dlg_escape_sequence(style, buf);
    288 
    289 fprintf(stream, "%s", buf);
    290 va_list args;
    291 va_start(args, format);
    292 int ret = dlg_vfprintf(stream, format, args);
    293 va_end(args);
    294 fprintf(stream, "%s", dlg_reset_sequence);
    295 return ret;
    296 }
    297 
    298 void dlg_generic_output(dlg_generic_output_handler output, void* data,
    299 	unsigned int features, const struct dlg_origin* origin, const char* string,
    300 	const struct dlg_style styles[6]) {
    301 // We never print any dynamic content below so we can be sure at compile
    302 // time that a buffer of size 64 is large enough.
    303 char format_buf[64];
    304 char* format = format_buf;
    305 
    306 if(features & dlg_output_style) {
    307 	format += sprintf(format, "%%s");
    308 }
    309 
    310 if(features & (dlg_output_time | dlg_output_file_line | dlg_output_tags | dlg_output_func)) {
    311 	format += sprintf(format, "[");
    312 }
    313 
    314 bool first_meta = true;
    315 if(features & dlg_output_time) {
    316 	format += sprintf(format, "%%h");
    317 	first_meta = false;
    318 }
    319 
    320 if(features & dlg_output_time_msecs) {
    321 	if(!first_meta) {
    322 		format += sprintf(format, ":");
    323 	}
    324 
    325 	format += sprintf(format, "%%m");
    326 	first_meta = false;
    327 }
    328 
    329 if(features & dlg_output_file_line) {
    330 	if(!first_meta) {
    331 		format += sprintf(format, " ");
    332 	}
    333 
    334 	format += sprintf(format, "%%o");
    335 	first_meta = false;
    336 }
    337 
    338 if(features & dlg_output_func) {
    339 	if(!first_meta) {
    340 		format += sprintf(format, " ");
    341 	}
    342 
    343 	format += sprintf(format, "%%f");
    344 	first_meta = false;
    345 }
    346 
    347 if(features & dlg_output_tags) {
    348 	if(!first_meta) {
    349 		format += sprintf(format, " ");
    350 	}
    351 
    352 	format += sprintf(format, "{%%t}");
    353 	first_meta = false;
    354 }
    355 
    356 if(features & (dlg_output_time | dlg_output_file_line | dlg_output_tags | dlg_output_func)) {
    357 	format += sprintf(format, "] ");
    358 }
    359 
    360 format += sprintf(format, "%%c");
    361 
    362 if(features & dlg_output_newline) {
    363 	format += sprintf(format, "\n");
    364 }
    365 
    366 *format = '\0';
    367 dlg_generic_outputf(output, data, format_buf, origin, string, styles);
    368 }
    369 
    370 void dlg_generic_outputf(dlg_generic_output_handler output, void* data,
    371 	const char* format_string, const struct dlg_origin* origin, const char* string,
    372 	const struct dlg_style styles[6]) {
    373 bool reset_style = false;
    374 for(const char* it = format_string; *it; it++) {
    375 	if(*it != '%') {
    376 		output(data, "%c", *it);
    377 		continue;
    378 	}
    379 
    380 	char next = *(it + 1); // must be valid since *it is not '\0'
    381 	if(next == 'h') {
    382 		time_t t = time(NULL);
    383 		struct tm tm_info;
    384 
    385 #ifdef DLG_OS_WIN
    386 		if(localtime_s(&tm_info, &t)) {
    387 #else
    388 		if(!localtime_r(&t, &tm_info)) {
    389 #endif
    390 			output(data, "<DATE ERROR>");
    391 		} else {
    392 			char timebuf[32];
    393 			strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm_info);
    394 			output(data, "%s", timebuf);
    395 		}
    396 		it++;
    397 	} else if(next == 'm') {
    398 		output(data, "%06d", get_msecs());
    399 		it++;
    400 	} else if(next == 't') {
    401 		bool first_tag = true;
    402 		for(const char** tags = origin->tags; *tags; ++tags) {
    403 			if(!first_tag) {
    404 				output(data, ", ");
    405 			}
    406 
    407 			output(data, "%s", *tags);
    408 			first_tag = false;
    409 		}
    410 		++it;
    411 	} else if(next == 'f') {
    412 		output(data, "%s", origin->func);
    413 		++it;
    414 	} else if(next == 'o') {
    415 		output(data, "%s:%u", origin->file, origin->line);
    416 		++it;
    417 	} else if(next == 's') {
    418 		char buf[12];
    419 		dlg_escape_sequence(styles[origin->level], buf);
    420 		output(data, "%s", buf);
    421 		reset_style = true;
    422 		++it;
    423 	} else if(next == 'r') {
    424 		output(data, "%s", dlg_reset_sequence);
    425 		reset_style = false;
    426 		++it;
    427 	} else if(next == 'c') {
    428 		if(origin->expr && string) {
    429 			output(data, "assertion '%s' failed: '%s'", origin->expr, string);
    430 		} else if(origin->expr) {
    431 			output(data, "assertion '%s' failed", origin->expr);
    432 		} else if(string) {
    433 			output(data, "%s", string);
    434 		}
    435 		++it;
    436 	} else if(next == '%') {
    437 		output(data, "%s", "%");
    438 		++it;
    439 	} else {
    440 		// in this case it's a '%' without known format specifier following
    441 		output(data, "%s", "%");
    442 	}
    443 }
    444 
    445 if(reset_style) {
    446 	output(data, "%s", dlg_reset_sequence);
    447 }
    448 }
    449 
    450 struct buf {
    451 char* buf;
    452 size_t* size;
    453 };
    454 
    455 static void print_size(void* size, const char* format, ...) {
    456 va_list args;
    457 va_start(args, format);
    458 
    459 int ret = vsnprintf(NULL, 0, format, args);
    460 va_end(args);
    461 
    462 if(ret > 0) {
    463 	*((size_t*) size) += ret;
    464 }
    465 }
    466 
    467 static void print_buf(void* dbuf, const char* format, ...) {
    468 struct buf* buf = (struct buf*) dbuf;
    469 va_list args;
    470 va_start(args, format);
    471 
    472 int printed = vsnprintf(buf->buf, *buf->size, format, args);
    473 va_end(args);
    474 
    475 if(printed > 0) {
    476 	*buf->size -= printed;
    477 	buf->buf += printed;
    478 }
    479 }
    480 
    481 void dlg_generic_output_buf(char* buf, size_t* size, unsigned int features,
    482 	const struct dlg_origin* origin, const char* string,
    483 	const struct dlg_style styles[6]) {
    484 if(buf) {
    485 	struct buf mbuf;
    486 	mbuf.buf = buf;
    487 	mbuf.size = size;
    488 	dlg_generic_output(print_buf, &mbuf, features, origin, string, styles);
    489 } else {
    490 	*size = 0;
    491 	dlg_generic_output(print_size, size, features, origin, string, styles);
    492 }
    493 }
    494 
    495 void dlg_generic_outputf_buf(char* buf, size_t* size, const char* format_string,
    496 	const struct dlg_origin* origin, const char* string,
    497 	const struct dlg_style styles[6]) {
    498 if(buf) {
    499 	struct buf mbuf;
    500 	mbuf.buf = buf;
    501 	mbuf.size = size;
    502 	dlg_generic_outputf(print_buf, &mbuf, format_string, origin, string, styles);
    503 } else {
    504 	*size = 0;
    505 	dlg_generic_outputf(print_size, size, format_string, origin, string, styles);
    506 }
    507 }
    508 
    509 static void print_stream(void* stream, const char* format, ...) {
    510 va_list args;
    511 va_start(args, format);
    512 dlg_vfprintf((FILE*) stream, format, args);
    513 va_end(args);
    514 }
    515 
    516 void dlg_generic_output_stream(FILE* stream, unsigned int features,
    517 	const struct dlg_origin* origin, const char* string,
    518 	const struct dlg_style styles[6]) {
    519 stream = stream ? stream : stdout;
    520 if(features & dlg_output_threadsafe) {
    521 	lock_file(stream);
    522 }
    523 
    524 dlg_generic_output(print_stream, stream, features, origin, string, styles);
    525 if(features & dlg_output_threadsafe) {
    526 	unlock_file(stream);
    527 }
    528 }
    529 
    530 void dlg_generic_outputf_stream(FILE* stream, const char* format_string,
    531 	const struct dlg_origin* origin, const char* string,
    532 	const struct dlg_style styles[6], bool lock_stream) {
    533 stream = stream ? stream : stdout;
    534 if(lock_stream) {
    535 	lock_file(stream);
    536 }
    537 
    538 dlg_generic_outputf(print_stream, stream, format_string, origin, string, styles);
    539 if(lock_stream) {
    540 	unlock_file(stream);
    541 }
    542 }
    543 
    544 void dlg_default_output(const struct dlg_origin* origin, const char* string, void* data) {
    545 FILE* stream = data ? (FILE*) data : stdout;
    546 unsigned int features = dlg_output_file_line |
    547 	dlg_output_newline |
    548 	dlg_output_threadsafe;
    549 
    550 #ifdef DLG_DEFAULT_OUTPUT_ALWAYS_COLOR
    551 dlg_win_init_ansi();
    552 features |= dlg_output_style;
    553 #else
    554 if(dlg_is_tty(stream) && dlg_win_init_ansi()) {
    555 	features |= dlg_output_style;
    556 }
    557 #endif
    558 
    559 dlg_generic_output_stream(stream, features, origin, string, dlg_default_output_styles);
    560 fflush(stream);
    561 }
    562 
    563 bool dlg_win_init_ansi(void) {
    564 #if defined(DLG_OS_WIN) && defined(DLG_WIN_CONSOLE)
    565 // TODO: use init once
    566 static volatile LONG status = 0;
    567 LONG res = InterlockedCompareExchange(&status, 1, 0);
    568 if(res == 0) { // not initialized
    569 	InterlockedExchange(&status, 3 + init_ansi_console());
    570 }
    571 
    572 while(status == 1); // currently initialized in another thread, spinlock
    573 return (status == 4);
    574 #else
    575 return true;
    576 #endif
    577 }
    578 
    579 // small dynamic vec/array implementation
    580 // Since the macros vec_init and vec_add[c]/vec_push might
    581 // change the pointers value it must not be referenced somewhere else.
    582 #define vec__raw(vec) (((unsigned int*) vec) - 2)
    583 
    584 static void* vec_do_create(unsigned int typesize, unsigned int cap, unsigned int size) {
    585 unsigned long a = (size > cap) ? size : cap;
    586 void* ptr = xalloc(2 * sizeof(unsigned int) + a * typesize);
    587 unsigned int* begin = (unsigned int*) ptr;
    588 begin[0] = size * typesize;
    589 begin[1] = a * typesize;
    590 return begin + 2;
    591 }
    592 
    593 // NOTE: can be more efficient if we are allowed to reorder vector
    594 static void vec_do_erase(void* vec, unsigned int pos, unsigned int size) {
    595 unsigned int* begin = vec__raw(vec);
    596 begin[0] -= size;
    597 char* buf = (char*) vec;
    598 memcpy(buf + pos, buf + pos + size, size);
    599 }
    600 
    601 static void* vec_do_add(void** vec, unsigned int size) {
    602 unsigned int* begin = vec__raw(*vec);
    603 unsigned int needed = begin[0] + size;
    604 if(needed >= begin[1]) {
    605 	void* ptr = xrealloc(begin, sizeof(unsigned int) * 2 + needed * 2);
    606 	begin = (unsigned int*) ptr;
    607 	begin[1] = needed * 2;
    608 	(*vec) = begin + 2;
    609 }
    610 
    611 void* ptr = ((char*) (*vec)) + begin[0];
    612 begin[0] += size;
    613 return ptr;
    614 }
    615 
    616 #define vec_create(type, size) (type*) vec_do_create(sizeof(type), size * 2, size)
    617 #define vec_create_reserve(type, size, capacity) (type*) vec_do_create(sizeof(type), capcity, size)
    618 #define vec_init(array, size) array = vec_do_create(sizeof(*array), size * 2, size)
    619 #define vec_init_reserve(array, size, capacity) *((void**) &array) = vec_do_create(sizeof(*array), capacity, size)
    620 #define vec_free(vec) (free((vec) ? vec__raw(vec) : NULL), vec = NULL)
    621 #define vec_erase_range(vec, pos, count) vec_do_erase(vec, pos * sizeof(*vec), count * sizeof(*vec))
    622 #define vec_erase(vec, pos) vec_do_erase(vec, pos * sizeof(*vec), sizeof(*vec))
    623 #define vec_size(vec) (vec__raw(vec)[0] / sizeof(*vec))
    624 #define vec_capacity(vec) (vec_raw(vec)[1] / sizeof(*vec))
    625 #define vec_add(vec) vec_do_add((void**) &vec, sizeof(*vec))
    626 #define vec_addc(vec, count) (vec_do_add((void**) &vec, sizeof(*vec) * count))
    627 #define vec_push(vec, value) (vec_do_add((void**) &vec, sizeof(*vec)), vec_last(vec) = (value))
    628 #define vec_pop(vec) (vec__raw(vec)[0] -= sizeof(*vec))
    629 #define vec_popc(vec, count) (vec__raw(vec)[0] -= sizeof(*vec) * count)
    630 #define vec_clear(vec) (vec__raw(vec)[0] = 0)
    631 #define vec_last(vec) (vec[vec_size(vec) - 1])
    632 
    633 static struct dlg_data* dlg_create_data(void) {
    634 struct dlg_data* data = (struct dlg_data*) xalloc(sizeof(struct dlg_data));
    635 vec_init_reserve(data->tags, 0, 20);
    636 vec_init_reserve(data->pairs, 0, 20);
    637 data->buffer_size = 100;
    638 data->buffer = (char*) xalloc(data->buffer_size);
    639 return data;
    640 }
    641 
    642 static void dlg_free_data(void* ddata) {
    643 struct dlg_data* data = (struct dlg_data*) ddata;
    644 if(data) {
    645 	vec_free(data->pairs);
    646 	vec_free(data->tags);
    647 	free(data->buffer);
    648 	free(data);
    649 }
    650 }
    651 
    652 void dlg_add_tag(const char* tag, const char* func) {
    653 struct dlg_data* data = dlg_data();
    654 struct dlg_tag_func_pair* pair =
    655 	(struct dlg_tag_func_pair*) vec_add(data->pairs);
    656 pair->tag = tag;
    657 pair->func = func;
    658 }
    659 
    660 bool dlg_remove_tag(const char* tag, const char* func) {
    661 struct dlg_data* data = dlg_data();
    662 for(unsigned int i = 0; i < vec_size(data->pairs); ++i) {
    663 	if(data->pairs[i].func == func && data->pairs[i].tag == tag) {
    664 		vec_erase(data->pairs, i);
    665 		return true;
    666 	}
    667 }
    668 
    669 return false;
    670 }
    671 
    672 char** dlg_thread_buffer(size_t** size) {
    673 struct dlg_data* data = dlg_data();
    674 if(size) {
    675 	*size = &data->buffer_size;
    676 }
    677 return &data->buffer;
    678 }
    679 
    680 void dlg_set_handler(dlg_handler handler, void* data) {
    681 g_handler = handler;
    682 g_data = data;
    683 }
    684 
    685 dlg_handler dlg_get_handler(void** data) {
    686 *data = g_data;
    687 return g_handler;
    688 }
    689 
    690 const char* dlg__printf_format(const char* str, ...) {
    691 va_list vlist;
    692 va_start(vlist, str);
    693 
    694 va_list vlistcopy;
    695 va_copy(vlistcopy, vlist);
    696 int needed = vsnprintf(NULL, 0, str, vlist);
    697 if(needed < 0) {
    698 	printf("dlg__printf_format: invalid format given\n");
    699 	va_end(vlist);
    700 	va_end(vlistcopy);
    701 	return NULL;
    702 }
    703 
    704 va_end(vlist);
    705 
    706 size_t* buf_size;
    707 char** buf = dlg_thread_buffer(&buf_size);
    708 if(*buf_size <= (unsigned int) needed) {
    709 	*buf_size = (needed + 1) * 2;
    710 	*buf = (char*) xrealloc(*buf, *buf_size);
    711 }
    712 
    713 vsnprintf(*buf, *buf_size, str, vlistcopy);
    714 va_end(vlistcopy);
    715 
    716 return *buf;
    717 }
    718 
    719 void dlg__do_log(enum dlg_level lvl, const char* const* tags, const char* file, int line,
    720 	const char* func, const char* string, const char* expr) {
    721 struct dlg_data* data = dlg_data();
    722 unsigned int tag_count = 0;
    723 
    724 // push default tags
    725 while(tags[tag_count]) {
    726 	vec_push(data->tags, tags[tag_count++]);
    727 }
    728 
    729 // push current global tags
    730 for(size_t i = 0; i < vec_size(data->pairs); ++i) {
    731 	const struct dlg_tag_func_pair pair = data->pairs[i];
    732 	if(pair.func == NULL || !strcmp(pair.func, func)) {
    733 		vec_push(data->tags, pair.tag);
    734 	}
    735 }
    736 
    737 // push call-specific tags, skip first terminating NULL
    738 ++tag_count;
    739 while(tags[tag_count]) {
    740 	vec_push(data->tags, tags[tag_count++]);
    741 }
    742 
    743 vec_push(data->tags, NULL); // terminating NULL
    744 struct dlg_origin origin;
    745 origin.level = lvl;
    746 origin.file = file;
    747 origin.line = line;
    748 origin.func = func;
    749 origin.expr = expr;
    750 origin.tags = data->tags;
    751 
    752 g_handler(&origin, string, g_data);
    753 vec_clear(data->tags);
    754 }
    755 
    756 #ifdef _MSC_VER
    757 // shitty msvc compatbility
    758 // meson gives us sane paths (separated by '/') while on MSVC,
    759 // __FILE__ contains a '\\' separator.
    760 static bool path_same(char a, char b) {
    761 return (a == b) ||
    762 	(a == '/' && b == '\\') ||
    763 	(a == '\\' && b == '/');
    764 }
    765 #else
    766 
    767 static inline bool path_same(char a, char b) {
    768 return a == b;
    769 }
    770 
    771 #endif
    772 
    773 const char* dlg__strip_root_path(const char* file, const char* base) {
    774 if(!file) {
    775 	return NULL;
    776 }
    777 
    778 const char* saved = file;
    779 if(*file == '.') { // relative path detected
    780 	while(*(++file) == '.' || *file == '/' || *file == '\\');
    781 	if(*file == '\0') { // weird case: purely relative path without file
    782 		return saved;
    783 	}
    784 
    785 	return file;
    786 }
    787 
    788 // strip base from file if it is given
    789 if(base) {
    790 	char fn = *file;
    791 	char bn = *base;
    792 	while(bn != '\0' && path_same(fn, bn)) {
    793 		fn = *(++file);
    794 		bn = *(++base);
    795 	}
    796 
    797 	if(fn == '\0' || bn != '\0') { // weird case: base isn't prefix of file
    798 		return saved;
    799 	}
    800 }
    801 
    802 return file;
    803 }