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