spdx tags for c and h files
[protos/libecoli.git] / lib / main.c
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright (c) 2016, Olivier MATZ <zer0@droids-corp.org>
3  */
4
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <assert.h>
8 #include <getopt.h>
9 #include <limits.h>
10 #include <execinfo.h>
11 #include <errno.h>
12
13 #include <ecoli_init.h>
14 #include <ecoli_log.h>
15 #include <ecoli_test.h>
16 #include <ecoli_malloc.h>
17
18 EC_LOG_TYPE_REGISTER(main);
19
20 #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \
21                 ((size_t)(!(sizeof(x) % sizeof(0[x])))))
22
23 static int log_level = EC_LOG_INFO;
24 static int alloc_fail_proba = 0;
25 static int seed = 0;
26 static size_t alloc_success = 0;
27
28 static const char ec_short_options[] =
29         "h"  /* help */
30         "l:" /* log-level */
31         "r:" /* random-alloc-fail */
32         "s:" /* seed */
33         ;
34
35 #define EC_OPT_HELP "help"
36 #define EC_OPT_LOG_LEVEL "log-level"
37 #define EC_OPT_RANDOM_ALLOC_FAIL "random-alloc-fail"
38 #define EC_OPT_SEED "seed"
39
40 static const struct option ec_long_options[] = {
41         {EC_OPT_HELP, 1, NULL, 'h'},
42         {EC_OPT_LOG_LEVEL, 1, NULL, 'l'},
43         {EC_OPT_RANDOM_ALLOC_FAIL, 1, NULL, 'r'},
44         {EC_OPT_SEED, 1, NULL, 's'},
45         {NULL, 0, NULL, 0}
46 };
47
48 static void usage(const char *prgname)
49 {
50         printf("%s [options] [test1 test2 test3...]\n"
51                 "  -h\n"
52                 "  --"EC_OPT_HELP"\n"
53                 "      Show this help.\n"
54                 "  -l <level>\n"
55                 "  --"EC_OPT_LOG_LEVEL"=<level>\n"
56                 "      Set log level (0 = no log, 7 = verbose).\n"
57                 "  -r <probability>\n"
58                 "  --"EC_OPT_RANDOM_ALLOC_FAIL"=<probability>\n"
59                 "      Cause malloc to fail randomly. This helps to debug\n"
60                 "      leaks or crashes in error cases. The probability is\n"
61                 "      between 0 and 100.\n"
62                 "  -s <seed>\n"
63                 "  --seed=<seed>\n"
64                 "      Seeds the random number generator. Default is 0.\n"
65                 , prgname);
66 }
67
68 static int
69 parse_int(const char *s, int min, int max, int *ret, unsigned int base)
70 {
71         char *end = NULL;
72         long long n;
73
74         n = strtoll(s, &end, base);
75         if ((s[0] == '\0') || (end == NULL) || (*end != '\0'))
76                 return -1;
77         if (n < min)
78                 return -1;
79         if (n > max)
80                 return -1;
81
82         *ret = n;
83         return 0;
84 }
85
86 static int parse_args(int argc, char **argv)
87 {
88         int ret, opt;
89
90         while ((opt = getopt_long(argc, argv, ec_short_options,
91                                 ec_long_options, NULL)) != EOF) {
92
93                 switch (opt) {
94                 case 'h': /* help */
95                         usage(argv[0]);
96                         exit(0);
97
98                 case 'l': /* log-level */
99                         if (parse_int(optarg, EC_LOG_EMERG,
100                                         EC_LOG_DEBUG, &log_level, 10) < 0) {
101                                 printf("Invalid log value\n");
102                                 usage(argv[0]);
103                                 exit(1);
104                         }
105                         break;
106
107                 case 'r': /* random-alloc-fail */
108                         if (parse_int(optarg, 0, 100, &alloc_fail_proba,
109                                         10) < 0) {
110                                 printf("Invalid probability value\n");
111                                 usage(argv[0]);
112                                 exit(1);
113                         }
114                         break;
115
116                 case 's': /* seed */
117                         if (parse_int(optarg, 0, INT_MAX, &seed, 10) < 0) {
118                                 printf("Invalid seed value\n");
119                                 usage(argv[0]);
120                                 exit(1);
121                         }
122                         break;
123
124                 default:
125                         usage(argv[0]);
126                         return -1;
127                 }
128         }
129
130         ret = optind - 1;
131         optind = 1;
132
133         return ret;
134 }
135
136 TAILQ_HEAD(debug_alloc_hdr_list, debug_alloc_hdr);
137 static struct debug_alloc_hdr_list debug_alloc_hdr_list =
138         TAILQ_HEAD_INITIALIZER(debug_alloc_hdr_list);
139
140 #define STACK_SZ 16
141 struct debug_alloc_hdr {
142         TAILQ_ENTRY(debug_alloc_hdr) next;
143         const char *file;
144         unsigned int line;
145         size_t size;
146         void *stack[STACK_SZ];
147         int stacklen;
148         unsigned int cookie;
149 };
150
151 struct debug_alloc_ftr {
152         unsigned int cookie;
153 } __attribute__((packed));
154
155 static void *debug_malloc(size_t size, const char *file, unsigned int line)
156 {
157         struct debug_alloc_hdr *hdr;
158         struct debug_alloc_ftr *ftr;
159         size_t new_size = size + sizeof(*hdr) + sizeof(*ftr);
160         void *ret;
161         int r = random();
162         static int seq;
163
164         if (alloc_fail_proba != 0 && (r % 100) < alloc_fail_proba)
165                 hdr = NULL;
166         else
167                 hdr = malloc(new_size);
168
169         if (hdr == NULL) {
170                 ret = NULL;
171         } else {
172                 hdr->file = file;
173                 hdr->line = line;
174                 hdr->size = size;
175                 hdr->stacklen = backtrace(hdr->stack, COUNT_OF(hdr->stack));
176                 hdr->cookie = 0x12345678;
177                 TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, hdr, next);
178                 ret = hdr + 1;
179                 ftr = (struct debug_alloc_ftr *)(
180                         (char *)hdr + size + sizeof(*hdr));
181                 ftr->cookie = 0x87654321;
182         }
183
184         EC_LOG(EC_LOG_DEBUG, "%s:%d: info: malloc(%zd) -> %p seq=%d\n",
185                 file, line, size, ret, seq++);
186
187         if (ret)
188                 alloc_success++;
189         return ret;
190 }
191
192 static void debug_free(void *ptr, const char *file, unsigned int line)
193 {
194         struct debug_alloc_hdr *hdr, *h;
195         struct debug_alloc_ftr *ftr;
196
197         (void)file;
198         (void)line;
199
200         EC_LOG(EC_LOG_DEBUG, "%s:%d: info: free(%p)\n", file, line, ptr);
201
202         if (ptr == NULL)
203                 return;
204
205         hdr = (ptr - sizeof(*hdr));
206         if (hdr->cookie != 0x12345678) {
207                 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad start cookie\n",
208                         file, line, ptr);
209                 abort();
210         }
211
212         ftr = (ptr + hdr->size);
213         if (ftr->cookie != 0x87654321) {
214                 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad end cookie\n",
215                         file, line, ptr);
216                 abort();
217         }
218
219         TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
220                 if (h == hdr)
221                         break;
222         }
223
224         if (h == NULL) {
225                 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad ptr\n",
226                         file, line, ptr);
227                 abort();
228         }
229
230         TAILQ_REMOVE(&debug_alloc_hdr_list, hdr, next);
231         free(hdr);
232 }
233
234 static void *debug_realloc(void *ptr, size_t size, const char *file,
235         unsigned int line)
236 {
237         struct debug_alloc_hdr *hdr, *h;
238         struct debug_alloc_ftr *ftr;
239         size_t new_size = size + sizeof(*hdr) + sizeof(unsigned int);
240         void *ret;
241
242         if (ptr != NULL) {
243                 hdr =  (ptr - sizeof(*hdr));
244                 if (hdr->cookie != 0x12345678) {
245                         EC_LOG(EC_LOG_ERR,
246                                 "%s:%d: error: realloc(%p): bad start cookie\n",
247                                 file, line, ptr);
248                         abort();
249                 }
250
251                 ftr = (ptr + hdr->size);
252                 if (ftr->cookie != 0x87654321) {
253                         EC_LOG(EC_LOG_ERR,
254                                 "%s:%d: error: realloc(%p): bad end cookie\n",
255                                 file, line, ptr);
256                         abort();
257                 }
258
259                 TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
260                         if (h == hdr)
261                                 break;
262                 }
263
264                 if (h == NULL) {
265                         EC_LOG(EC_LOG_ERR, "%s:%d: error: realloc(%p): bad ptr\n",
266                                 file, line, ptr);
267                         abort();
268                 }
269
270                 TAILQ_REMOVE(&debug_alloc_hdr_list, h, next);
271                 hdr = realloc(hdr, new_size);
272                 if (hdr == NULL) {
273                         TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, h, next);
274                         ret = NULL;
275                 } else {
276                         ret = hdr + 1;
277                 }
278         } else {
279                 hdr = realloc(NULL, new_size);
280                 if (hdr == NULL)
281                         ret = NULL;
282                 else
283                         ret = hdr + 1;
284         }
285
286         if (hdr != NULL) {
287                 hdr->file = file;
288                 hdr->line = line;
289                 hdr->size = size;
290                 hdr->stacklen = backtrace(hdr->stack, COUNT_OF(hdr->stack));
291                 hdr->cookie = 0x12345678;
292                 TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, hdr, next);
293                 ftr = (struct debug_alloc_ftr *)(
294                         (char *)hdr + size + sizeof(*hdr));
295                 ftr->cookie = 0x87654321;
296         }
297
298         EC_LOG(EC_LOG_DEBUG, "%s:%d: info: realloc(%p, %zd) -> %p\n",
299                 file, line, ptr, size, ret);
300
301         if (ret)
302                 alloc_success++;
303         return ret;
304 }
305
306 static int debug_alloc_dump_leaks(void)
307 {
308         struct debug_alloc_hdr *hdr;
309         int i;
310         char **buffer;
311
312         EC_LOG(EC_LOG_INFO, "%zd successful allocations\n", alloc_success);
313
314         if (TAILQ_EMPTY(&debug_alloc_hdr_list))
315                 return 0;
316
317         TAILQ_FOREACH(hdr, &debug_alloc_hdr_list, next) {
318                 EC_LOG(EC_LOG_ERR,
319                         "%s:%d: error: memory leak size=%zd ptr=%p\n",
320                         hdr->file, hdr->line, hdr->size, hdr + 1);
321                 buffer = backtrace_symbols(hdr->stack, hdr->stacklen);
322                 if (buffer == NULL) {
323                         for (i = 0; i < hdr->stacklen; i++)
324                                 EC_LOG(EC_LOG_ERR, "  %p\n", hdr->stack[i]);
325                 } else {
326                         for (i = 0; i < hdr->stacklen; i++)
327                                 EC_LOG(EC_LOG_ERR, "  %s\n",
328                                         buffer ? buffer[i] : "unknown");
329                 }
330                 free(buffer);
331         }
332
333         EC_LOG(EC_LOG_ERR,
334                 "  missing static syms, use: addr2line -f -e <prog> <addr>\n");
335
336         return -1;
337 }
338
339 static int debug_log(int type, unsigned int level, void *opaque,
340                 const char *str)
341 {
342         (void)type;
343         (void)opaque;
344
345         if (level > (unsigned int)log_level)
346                 return 0;
347
348         if (printf("%s", str) < 0)
349                 return -1;
350
351         return 0;
352 }
353
354 int main(int argc, char **argv)
355 {
356         int i, ret = 0, leaks;
357
358         ret = parse_args(argc, argv);
359         if (ret < 0)
360                 return 1;
361
362         argc -= ret;
363         argv += ret;
364
365         srandom(seed);
366
367         /* register a new malloc to track memleaks */
368         TAILQ_INIT(&debug_alloc_hdr_list);
369         if (ec_malloc_register(debug_malloc, debug_free, debug_realloc) < 0) {
370                 EC_LOG(EC_LOG_ERR, "cannot register new malloc\n");
371                 return 1;
372         }
373
374         if (ec_init() < 0) {
375                 fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno));
376                 return 1;
377         }
378         ec_log_fct_register(debug_log, NULL);
379
380         ret = 0;
381         if (argc <= 1) {
382                 ret = ec_test_all();
383         } else {
384                 for (i = 1; i < argc; i++)
385                         ret |= ec_test_one(argv[i]);
386         }
387
388         leaks = debug_alloc_dump_leaks();
389
390         if (alloc_fail_proba == 0 && ret != 0) {
391                 printf("tests failed\n");
392                 return 1;
393         } else if (alloc_fail_proba != 0 && leaks != 0) {
394                 printf("tests failed (memory leak)\n");
395                 return 1;
396         }
397
398         printf("\ntests ok\n");
399
400         return 0;
401 }