]> git.neil.brown.name Git - edlib.git/blob - core-misc.c
TODO: clean out done items.
[edlib.git] / core-misc.c
1 /*
2  * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * Assorted utility functions used by edlib
6  *
7  */
8 #define _GNU_SOURCE /*  for asprintf */
9 #include <stdlib.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <signal.h>
13 #include <wchar.h>
14 #include <time.h>
15 #include <unistd.h>
16
17 #include "core.h"
18 #include "safe.h"
19 #include "list.h"
20 #include "misc.h"
21
22 void buf_init(struct buf *b safe)
23 {
24         b->size = 32;
25         b->b = malloc(b->size);
26         b->len = 0;
27 }
28
29 void buf_resize(struct buf *b safe, int size)
30 {
31         size += 1; /* we will nul-terminate */
32         if (size > b->size) {
33                 b->size = size;
34                 b->b = realloc(b->b, b->size);
35         }
36 }
37
38 void buf_concat_len(struct buf *b safe, const char *s safe, int l)
39 {
40
41         if (b->len + l >= b->size) {
42                 while (b->len + l >= b->size)
43                         b->size += 128;
44                 b->b = realloc(b->b, b->size);
45         }
46         memcpy(b->b + b->len, s, l);
47         b->len += l;
48         b->b[b->len] = 0;
49 }
50
51 void buf_concat(struct buf *b safe, const char *s safe)
52 {
53         int l = strlen(s);
54         buf_concat_len(b, s, l);
55 }
56
57 void buf_append(struct buf *b safe, wchar_t wch)
58 {
59         char t[5];
60
61         buf_concat(b, put_utf8(t, wch));
62 }
63
64 void buf_append_byte(struct buf *b safe, char c)
65 {
66         buf_concat_len(b, &c, 1);
67 }
68
69 /*
70  * performance measurements
71  */
72
73 static long long tstart[TIME__COUNT];
74 static int tcount[TIME__COUNT];
75 static long long tsum[TIME__COUNT];
76 static int stats_enabled = 1;
77
78 static time_t last_dump = 0;
79 static FILE *dump_file;
80
81 static void dump_key_hash(void);
82 static void dump_count_hash(void);
83 static void stat_dump(void);
84 static void dump_mem(void);
85
86 static const char *tnames[] = {
87         [TIME_KEY]     = "KEY",
88         [TIME_WINDOW]  = "WINDOW",
89         [TIME_READ]    = "READ",
90         [TIME_SIG]     = "SIG",
91         [TIME_TIMER]   = "TIMER",
92         [TIME_IDLE]    = "IDLE",
93         [TIME_REFRESH] = "REFRESH",
94         [TIME_MISC]    = "MISC",
95 };
96
97 #define NSEC 1000000000
98 void time_start(enum timetype type)
99 {
100         struct timespec start;
101         if (type < 0 || type >= TIME__COUNT || !stats_enabled)
102                 return;
103         clock_gettime(CLOCK_MONOTONIC, &start);
104         tstart[type] = start.tv_sec * NSEC + start.tv_nsec;
105 }
106
107 void time_stop(enum timetype type)
108 {
109         struct timespec stop;
110         long long nsec;
111
112         if (type < 0 || type >= TIME__COUNT || !stats_enabled)
113                 return;
114         if (!tstart[type])
115                 return;
116         clock_gettime(CLOCK_MONOTONIC, &stop);
117
118         nsec = (stop.tv_sec * NSEC + stop.tv_nsec) - tstart[type];
119         tstart[type] = 0;
120         tcount[type] += 1;
121         tsum[type] += nsec;
122
123         if (getenv("EDLIB_STATS_FAST")) {
124                 if (stop.tv_sec < last_dump + 5 || tcount[TIME_REFRESH] < 10)
125                         return;
126         } else {
127                 if (stop.tv_sec < last_dump + 30 || tcount[TIME_REFRESH] < 100)
128                         return;
129         }
130         if (last_dump == 0) {
131                 last_dump = stop.tv_sec;
132                 return;
133         }
134         if (!getenv("EDLIB_STATS")) {
135                 stats_enabled = 0;
136                 return;
137         }
138         last_dump = stop.tv_sec;
139         stat_dump();
140 }
141
142 static void stat_dump(void)
143 {
144         int i;
145
146         if (!dump_file) {
147                 char *fname = NULL;
148                 asprintf(&fname, ".edlib_stats-%d", getpid());
149                 if (!fname)
150                         fname = "/tmp/edlib_stats";
151                 dump_file = fopen(fname, "w");
152                 if (!dump_file) {
153                         stats_enabled = 0;
154                         return;
155                 }
156         }
157         fprintf(dump_file, "%ld:", (long)time(NULL));
158         for (i = 0; i< TIME__COUNT; i++) {
159                 fprintf(dump_file, " %s:%d:%lld", tnames[i], tcount[i],
160                         tsum[i] / (tcount[i]?:1));
161                 tcount[i] = 0;
162                 tsum[i] = 0;
163         }
164         dump_key_hash();
165         dump_count_hash();
166         fprintf(dump_file, "\n");
167         dump_mem();
168         fflush(dump_file);
169 }
170
171 inline static int qhash(char key, unsigned int start)
172 {
173         return (start ^ key) * 0x61C88647U;
174 }
175
176 static int hash_str(const char *key safe, int len)
177 {
178         int i;
179         int h = 0;
180
181         for (i = 0; (len < 0 || i < len) && key[i]; i++)
182                 h = qhash(key[i], h);
183         return h;
184 }
185
186 struct khash {
187         struct khash *next;
188         int hash;
189         long long tsum;
190         int tcount;
191         char name[1];
192 };
193
194 static struct khash *khashtab[1024];
195
196 static struct kstack {
197         long long tstart;
198         const char *name;
199 } kstack[20];
200 static int ktos = 0;
201
202 void time_start_key(const char *key safe)
203 {
204         struct timespec start;
205
206         if (!stats_enabled)
207                 return;
208         ktos += 1;
209         if (ktos > 20)
210                 return;
211         clock_gettime(CLOCK_MONOTONIC, &start);
212         kstack[ktos-1].tstart = start.tv_sec * NSEC + start.tv_nsec;
213         kstack[ktos-1].name = key;
214 }
215
216 static struct khash *hash_find(struct khash **table, const char *key safe)
217 {
218         struct khash *h, **hp;
219         int hash;
220
221         hash = hash_str(key, -1);
222         hp = &table[hash & 1023];
223         while ( (h = *hp) && (h->hash != hash || strcmp(h->name, key) != 0))
224                 hp = &h->next;
225         if (!h) {
226                 h = malloc(sizeof(*h) + strlen(key));
227                 h->hash = hash;
228                 h->tsum = 0;
229                 h->tcount = 0;
230                 strcpy(h->name, key);
231                 h->next = *hp;
232                 *hp = h;
233         }
234         return h;
235 }
236
237 void time_stop_key(const char *key safe)
238 {
239         struct timespec stop;
240         struct khash *h;
241
242         if (!stats_enabled)
243                 return;
244         if (ktos <= 0)
245                 abort();
246         ktos -= 1;
247         if (ktos >= 20)
248                 return;
249         if (key != kstack[ktos].name)
250                 abort();
251         clock_gettime(CLOCK_MONOTONIC, &stop);
252
253         h = hash_find(khashtab, key);
254         h->tcount += 1;
255         h->tsum += stop.tv_sec * NSEC + stop.tv_nsec - kstack[ktos].tstart;
256 }
257
258 static void dump_key_hash(void)
259 {
260         int i;
261         int cnt = 0;
262         int buckets = 0;
263         int max = 0;
264
265         for (i = 0; i < 1024; i++) {
266                 struct khash *h;
267                 int c = 0;
268                 for (h = khashtab[i]; h ; h = h->next) {
269                         c += 1;
270                         if (!h->tcount)
271                                 continue;
272                         fprintf(dump_file, " %s:%d:%lld",
273                                 h->name, h->tcount,
274                                 h->tsum / (h->tcount?:1));
275                         h->tcount = 0;
276                         h->tsum = 0;
277                 }
278                 cnt += c;
279                 buckets += !!c;
280                 if (c > max)
281                         max = c;
282         }
283         fprintf(dump_file, " khash:%d:%d:%d", cnt, buckets, max);
284 }
285
286 static struct khash *count_tab[1024];
287
288 void stat_count(char *name safe)
289 {
290         struct khash *h;
291
292         if (!stats_enabled)
293                 return;
294         h = hash_find(count_tab, name);
295         h->tcount += 1;
296 }
297
298 static void dump_count_hash(void)
299 {
300         int i;
301         int cnt = 0;
302         int buckets = 0;
303         int max = 0;
304
305         for (i = 0; i < 1024; i++) {
306                 struct khash *h;
307                 int c = 0;
308                 for (h = count_tab[i]; h ; h = h->next) {
309                         c += 1;
310                         fprintf(dump_file, " %s:%d:-",
311                                 h->name, h->tcount);
312                         h->tcount = 0;
313                         h->tsum = 0;
314                 }
315                 cnt += c;
316                 buckets += !!c;
317                 if (c > max)
318                         max = c;
319         }
320         fprintf(dump_file, " nhash:%d:%d:%d", cnt, buckets, max);
321 }
322
323 static void hash_free(struct khash **tab safe)
324 {
325         int i;
326
327         for (i = 0; i < 1024; i++) {
328                 struct khash *h;
329
330                 while ((h = tab[i]) != NULL) {
331                         tab[i] = h->next;
332                         free(h);
333                 }
334         }
335 }
336
337 void stat_free(void)
338 {
339         /* stats_enabled is only valid after 30 seconds, so
340          * so we need to check EDLIB_STATS directly
341          */
342         if (stats_enabled && getenv("EDLIB_STATS"))
343                 stat_dump();
344         hash_free(count_tab);
345         hash_free(khashtab);
346         stats_enabled = 0;
347 }
348
349 static LIST_HEAD(mem_pools);
350
351 void *safe do_alloc(struct mempool *pool safe, int size, int zero)
352 {
353         void *ret = malloc(size);
354
355         if (zero)
356                 memset(ret, 0, size);
357         pool->bytes += size;
358         pool->allocations += 1;
359         if (pool->bytes > pool->max_bytes)
360                 pool->max_bytes = pool->bytes;
361         if (list_empty(&pool->linkage))
362                 list_add(&pool->linkage, &mem_pools);
363         return ret;
364 }
365
366 void do_unalloc(struct mempool *pool safe, const void *obj, int size)
367 {
368         if (obj) {
369                 pool->bytes -= size;
370                 pool->allocations -= 1;
371                 free((void*)obj);
372         }
373 }
374
375 static void dump_mem(void)
376 {
377         struct mempool *p;
378
379         fprintf(dump_file, "mem:");
380         list_for_each_entry(p, &mem_pools, linkage)
381                 fprintf(dump_file, " %s:%ld(%ld):%ld",
382                         p->name, p->bytes, p->max_bytes, p->allocations);
383         fprintf(dump_file, "\n");
384 }
385
386 /* UTF-8 handling....
387  * - return wchar (wint_t) and advance pointer
388  * - append encoding to buf, advance pointer, decrease length
389  *
390  * UTF-8:
391  * - if it starts '0b0', it is a 7bit code point
392  * - if it starts '0b10' it is a non-initial byte and provides 6 bits.
393  * - if it starts '0b110' it is first of 2 and provides 5 of 11 bits
394  * - if it starts '0b1110' it is first of 3 and provides 4 of 16 bits
395  * - if it starts '0b11110' it is first of 4 and provides 3 of 21 bits.
396  */
397 wint_t get_utf8(const char **cpp safe, const char *end)
398 {
399         int tail = 0;
400         wint_t ret = 0;
401         const char *cp = *cpp;
402         unsigned char c;
403
404         if (!cp)
405                 return WEOF;
406         if (end && end <= cp)
407                 return WEOF;
408         c = (unsigned char)*cp++;
409         if (!c)
410                 return WEOF;
411         if (c < 0x80)
412                 ret = c;
413         else if (c < 0xc0)
414                 return WERR;
415         else if (c < 0xe0) {
416                 ret = c & 0x1f;
417                 tail = 1;
418         } else if (c < 0xf0) {
419                 ret = c & 0xf;
420                 tail = 2;
421         } else if (c < 0xf8) {
422                 ret = c & 0x7;
423                 tail = 3;
424         } else
425                 return WERR;
426         if (end && end < cp + tail)
427                 return WERR;
428         while (tail--) {
429                 c = *cp++;
430                 if ((c & 0xc0) != 0x80)
431                         return WERR;
432                 ret = (ret << 6) | (c & 0x3f);
433         }
434         *cpp = cp;
435         return ret;
436 }
437
438 char *safe put_utf8(char *buf safe, wchar_t ch)
439 {
440         char mask;
441         int l, i;
442
443         if (ch < 0x80) {
444                 l = 1;
445                 mask = 0x7f;
446         } else if (ch < 0x800) {
447                 l = 2;
448                 mask = 0x1f;
449         } else if (ch < 0x10000) {
450                 l = 3;
451                 mask = 0x0f;
452         } else if (ch < 0x200000) {
453                 l = 4;
454                 mask = 0x07;
455         } else
456                 l = 0;
457
458         for (i = 0 ; i < l; i++) {
459                 buf[i] = (ch >> ((l-1-i)*6)) & mask;
460                 buf[i] |= ~(mask+mask+1);
461                 mask = 0x3f;
462         }
463         buf[l] = 0;
464         return buf;
465 }
466
467 int utf8_strlen(const char *s safe)
468 {
469         int cnt = 0;
470
471         while (*s) {
472                 if ((*s & 0xc0) != 0x80)
473                         cnt += 1;
474                 s += 1;
475         }
476         return cnt;
477 }
478
479 int utf8_strnlen(const char *s safe, int n)
480 {
481         int cnt = 0;
482
483         while (*s && n > 0) {
484                 if ((*s & 0xc0) != 0x80)
485                         cnt += 1;
486                 s += 1;
487                 n -= 1;
488         }
489         return cnt;
490 }
491
492 int utf8_valid(const char *s safe)
493 {
494         wint_t c;
495
496         while ((c = get_utf8(&s, NULL)) != WEOF) {
497                 if (c == WERR ||
498                     c > 0x10FFFF)
499                         return 0;
500         }
501         return 1;
502 }
503
504 /*
505  * When walking backwards through a string, we need to round a point
506  * down to the start of a code-point.
507  * When reading a file into allocated chunks of memory, we want each chunk
508  * to hold a whole number of code points.
509  * For both of these needs, we have utf8_round_len which tries to reduce
510  * the given length to a code-point boundary, if possible.
511 *
512  * We only adjust the length if we can find a start-of-code-point in
513  * the last 4 bytes. (longest UTF-8 encoding of 21bit unicode is 4 bytes).
514  * A start of codepoint starts with 0b0 or 0b11, not 0b10.
515  */
516 int utf8_round_len(const char *text safe, int len)
517 {
518         /* The string at 'text' is *longer* than 'len', or
519          * at least text[len] is defined - it can be nul.  If
520          * [len] isn't the start of a new codepoint, and there
521          * is a start marker in the previous 4 bytes,
522          * move back to there.
523          */
524         int i = 0;
525         while (i <= len && i <=4)
526                 if ((text[len-i] & 0xC0) == 0x80)
527                         /* next byte is inside a UTF-8 code point, so
528                          * this isn't a good spot to end. Try further
529                          * back */
530                         i += 1;
531                 else
532                         return len-i;
533         return len;
534 }
535
536 static int _debugger_present = -1;
537 static void _sigtrap_handler(int signum)
538 {
539         _debugger_present = 0;
540         signal(SIGTRAP, SIG_DFL);
541 }
542
543 bool debugger_is_present(void)
544 {
545         if (_debugger_present < 0) {
546                 _debugger_present = 1;
547                 signal(SIGTRAP, _sigtrap_handler);
548                 raise(SIGTRAP);
549         }
550         return _debugger_present;
551 }
552
553 /* attr parsing */
554 const char *afind_val(const char **cp safe, const char *end)
555 {
556         const char *c = *cp;
557         const char *ret;
558
559         if (!c)
560                 return NULL;
561         if (!end)
562                 end = c + strlen(c);
563         while (c < end && *c != ':' && *c != ',')
564                 c++;
565         if (c == end) {
566                 *cp = NULL;
567                 return NULL;
568         }
569         if (*c == ',') {
570                 while (*c == ',' && c < end)
571                         c++;
572                 if (c == end) {
573                         *cp = NULL;
574                         return NULL;
575                 }
576                 *cp = c;
577                 return NULL;
578         }
579         c += 1;
580         ret = c;
581         while (c < end && *c != ',')
582                 c++;
583         while (c < end && *c == ',')
584                 c++;
585         if (c == end)
586                 c = NULL;
587         *cp = c;
588         return ret;
589 }
590
591 char *aupdate(char **cp safe, const char *v)
592 {
593         /* duplicate value at v and store in *cp, freeing what is there
594          * first
595          */
596         const char *end = v;
597
598         while (end && *end != ',' && *end >= ' ')
599                 end += 1;
600
601         free(*cp);
602         if (v)
603                 *cp = strndup(v, end-v);
604         else
605                 *cp = NULL;
606         return *cp;
607 }
608
609 bool amatch(const char *a safe, const char *m safe)
610 {
611         while (*a && *a == *m) {
612                 a += 1;
613                 m += 1;
614         }
615         if (*m)
616                 /* Didn't match all of m */
617                 return False;
618         if (*a != ':' && *a != ',' && *a >= ' ')
619                 /* Didn't match all of a */
620                 return False;
621         return True;
622 }
623
624 bool aprefix(const char *a safe, const char *m safe)
625 {
626         while (*a && *a == *m) {
627                 a += 1;
628                 m += 1;
629         }
630         if (*m)
631                 /* Didn't match all of m */
632                 return False;
633         return True;
634 }
635
636 long anum(const char *v safe)
637 {
638         char *end = NULL;
639         long ret = strtol(v, &end, 10);
640         if (end == v || !end ||
641             (*end != ',' && *end >= ' '))
642                 /* Not a valid number - use zero */
643                 return 0;
644         return ret;
645 }