fix cmd node
[protos/libecoli.git] / lib / main.c
1 /*
2  * Copyright (c) 2016, Olivier MATZ <zer0@droids-corp.org>
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the name of the University of California, Berkeley nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <assert.h>
31 #include <getopt.h>
32 #include <limits.h>
33 #include <execinfo.h>
34 #include <errno.h>
35
36 #include <ecoli_init.h>
37 #include <ecoli_log.h>
38 #include <ecoli_test.h>
39 #include <ecoli_malloc.h>
40
41 EC_LOG_TYPE_REGISTER(main);
42
43 #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \
44                 ((size_t)(!(sizeof(x) % sizeof(0[x])))))
45
46 static int log_level = EC_LOG_INFO;
47 static int alloc_fail_proba = 0;
48 static int seed = 0;
49 static size_t alloc_success = 0;
50
51 static const char ec_short_options[] =
52         "h"  /* help */
53         "l:" /* log-level */
54         "r:" /* random-alloc-fail */
55         "s:" /* seed */
56         ;
57
58 #define EC_OPT_HELP "help"
59 #define EC_OPT_LOG_LEVEL "log-level"
60 #define EC_OPT_RANDOM_ALLOC_FAIL "random-alloc-fail"
61 #define EC_OPT_SEED "seed"
62
63 static const struct option ec_long_options[] = {
64         {EC_OPT_HELP, 1, NULL, 'h'},
65         {EC_OPT_LOG_LEVEL, 1, NULL, 'l'},
66         {EC_OPT_RANDOM_ALLOC_FAIL, 1, NULL, 'r'},
67         {EC_OPT_SEED, 1, NULL, 's'},
68         {NULL, 0, NULL, 0}
69 };
70
71 static void usage(const char *prgname)
72 {
73         printf("%s [options] [test1 test2 test3...]\n"
74                 "  -h\n"
75                 "  --"EC_OPT_HELP"\n"
76                 "      Show this help.\n"
77                 "  -l <level>\n"
78                 "  --"EC_OPT_LOG_LEVEL"=<level>\n"
79                 "      Set log level (0 = no log, 7 = verbose).\n"
80                 "  -r <probability>\n"
81                 "  --"EC_OPT_RANDOM_ALLOC_FAIL"=<probability>\n"
82                 "      Cause malloc to fail randomly. This helps to debug\n"
83                 "      leaks or crashes in error cases. The probability is\n"
84                 "      between 0 and 100.\n"
85                 "  -s <seed>\n"
86                 "  --seed=<seed>\n"
87                 "      Seeds the random number generator. Default is 0.\n"
88                 , prgname);
89 }
90
91 static int
92 parse_int(const char *s, int min, int max, int *ret, unsigned int base)
93 {
94         char *end = NULL;
95         long long n;
96
97         n = strtoll(s, &end, base);
98         if ((s[0] == '\0') || (end == NULL) || (*end != '\0'))
99                 return -1;
100         if (n < min)
101                 return -1;
102         if (n > max)
103                 return -1;
104
105         *ret = n;
106         return 0;
107 }
108
109 static int parse_args(int argc, char **argv)
110 {
111         int ret, opt;
112
113         while ((opt = getopt_long(argc, argv, ec_short_options,
114                                 ec_long_options, NULL)) != EOF) {
115
116                 switch (opt) {
117                 case 'h': /* help */
118                         usage(argv[0]);
119                         exit(0);
120
121                 case 'l': /* log-level */
122                         if (parse_int(optarg, EC_LOG_EMERG,
123                                         EC_LOG_DEBUG, &log_level, 10) < 0) {
124                                 printf("Invalid log value\n");
125                                 usage(argv[0]);
126                                 exit(1);
127                         }
128                         break;
129
130                 case 'r': /* random-alloc-fail */
131                         if (parse_int(optarg, 0, 100, &alloc_fail_proba,
132                                         10) < 0) {
133                                 printf("Invalid probability value\n");
134                                 usage(argv[0]);
135                                 exit(1);
136                         }
137                         break;
138
139                 case 's': /* seed */
140                         if (parse_int(optarg, 0, INT_MAX, &seed, 10) < 0) {
141                                 printf("Invalid seed value\n");
142                                 usage(argv[0]);
143                                 exit(1);
144                         }
145                         break;
146
147                 default:
148                         usage(argv[0]);
149                         return -1;
150                 }
151         }
152
153         ret = optind - 1;
154         optind = 1;
155
156         return ret;
157 }
158
159 TAILQ_HEAD(debug_alloc_hdr_list, debug_alloc_hdr);
160 static struct debug_alloc_hdr_list debug_alloc_hdr_list =
161         TAILQ_HEAD_INITIALIZER(debug_alloc_hdr_list);
162
163 #define STACK_SZ 16
164 struct debug_alloc_hdr {
165         TAILQ_ENTRY(debug_alloc_hdr) next;
166         const char *file;
167         unsigned int line;
168         size_t size;
169         void *stack[STACK_SZ];
170         int stacklen;
171         unsigned int cookie;
172 };
173
174 struct debug_alloc_ftr {
175         unsigned int cookie;
176 } __attribute__((packed));
177
178 static void *debug_malloc(size_t size, const char *file, unsigned int line)
179 {
180         struct debug_alloc_hdr *hdr;
181         struct debug_alloc_ftr *ftr;
182         size_t new_size = size + sizeof(*hdr) + sizeof(*ftr);
183         void *ret;
184         int r = random();
185         static int seq;
186
187         if (alloc_fail_proba != 0 && (r % 100) < alloc_fail_proba)
188                 hdr = NULL;
189         else
190                 hdr = malloc(new_size);
191
192         if (hdr == NULL) {
193                 ret = NULL;
194         } else {
195                 hdr->file = file;
196                 hdr->line = line;
197                 hdr->size = size;
198                 hdr->stacklen = backtrace(hdr->stack, COUNT_OF(hdr->stack));
199                 hdr->cookie = 0x12345678;
200                 TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, hdr, next);
201                 ret = hdr + 1;
202                 ftr = (struct debug_alloc_ftr *)(
203                         (char *)hdr + size + sizeof(*hdr));
204                 ftr->cookie = 0x87654321;
205         }
206
207         EC_LOG(EC_LOG_DEBUG, "%s:%d: info: malloc(%zd) -> %p seq=%d\n",
208                 file, line, size, ret, seq++);
209         if (seq == 100)
210                 printf("here\n");
211
212         if (ret)
213                 alloc_success++;
214         return ret;
215 }
216
217 static void debug_free(void *ptr, const char *file, unsigned int line)
218 {
219         struct debug_alloc_hdr *hdr, *h;
220         struct debug_alloc_ftr *ftr;
221
222         (void)file;
223         (void)line;
224
225         EC_LOG(EC_LOG_DEBUG, "%s:%d: info: free(%p)\n", file, line, ptr);
226
227         if (ptr == NULL)
228                 return;
229
230         hdr = (ptr - sizeof(*hdr));
231         if (hdr->cookie != 0x12345678) {
232                 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad start cookie\n",
233                         file, line, ptr);
234                 abort();
235         }
236
237         ftr = (ptr + hdr->size);
238         if (ftr->cookie != 0x87654321) {
239                 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad end cookie\n",
240                         file, line, ptr);
241                 abort();
242         }
243
244         TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
245                 if (h == hdr)
246                         break;
247         }
248
249         if (h == NULL) {
250                 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad ptr\n",
251                         file, line, ptr);
252                 abort();
253         }
254
255         TAILQ_REMOVE(&debug_alloc_hdr_list, hdr, next);
256         free(hdr);
257 }
258
259 static void *debug_realloc(void *ptr, size_t size, const char *file,
260         unsigned int line)
261 {
262         struct debug_alloc_hdr *hdr, *h;
263         struct debug_alloc_ftr *ftr;
264         size_t new_size = size + sizeof(*hdr) + sizeof(unsigned int);
265         void *ret;
266
267         if (ptr != NULL) {
268                 hdr =  (ptr - sizeof(*hdr));
269                 if (hdr->cookie != 0x12345678) {
270                         EC_LOG(EC_LOG_ERR,
271                                 "%s:%d: error: realloc(%p): bad start cookie\n",
272                                 file, line, ptr);
273                         abort();
274                 }
275
276                 ftr = (ptr + hdr->size);
277                 if (ftr->cookie != 0x87654321) {
278                         EC_LOG(EC_LOG_ERR,
279                                 "%s:%d: error: realloc(%p): bad end cookie\n",
280                                 file, line, ptr);
281                         abort();
282                 }
283
284                 TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
285                         if (h == hdr)
286                                 break;
287                 }
288
289                 if (h == NULL) {
290                         EC_LOG(EC_LOG_ERR, "%s:%d: error: realloc(%p): bad ptr\n",
291                                 file, line, ptr);
292                         abort();
293                 }
294
295                 TAILQ_REMOVE(&debug_alloc_hdr_list, h, next);
296                 hdr = realloc(hdr, new_size);
297                 if (hdr == NULL) {
298                         TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, h, next);
299                         ret = NULL;
300                 } else {
301                         ret = hdr + 1;
302                 }
303         } else {
304                 hdr = realloc(NULL, new_size);
305                 if (hdr == NULL)
306                         ret = NULL;
307                 else
308                         ret = hdr + 1;
309         }
310
311         if (hdr != NULL) {
312                 hdr->file = file;
313                 hdr->line = line;
314                 hdr->size = size;
315                 hdr->stacklen = backtrace(hdr->stack, COUNT_OF(hdr->stack));
316                 hdr->cookie = 0x12345678;
317                 TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, hdr, next);
318                 ftr = (struct debug_alloc_ftr *)(
319                         (char *)hdr + size + sizeof(*hdr));
320                 ftr->cookie = 0x87654321;
321         }
322
323         EC_LOG(EC_LOG_DEBUG, "%s:%d: info: realloc(%p, %zd) -> %p\n",
324                 file, line, ptr, size, ret);
325
326         if (ret)
327                 alloc_success++;
328         return ret;
329 }
330
331 static int debug_alloc_dump_leaks(void)
332 {
333         struct debug_alloc_hdr *hdr;
334         int i;
335         char **buffer;
336
337         EC_LOG(EC_LOG_INFO, "%zd successful allocations\n", alloc_success);
338
339         if (TAILQ_EMPTY(&debug_alloc_hdr_list))
340                 return 0;
341
342         TAILQ_FOREACH(hdr, &debug_alloc_hdr_list, next) {
343                 EC_LOG(EC_LOG_ERR,
344                         "%s:%d: error: memory leak size=%zd ptr=%p\n",
345                         hdr->file, hdr->line, hdr->size, hdr + 1);
346                 buffer = backtrace_symbols(hdr->stack, hdr->stacklen);
347                 if (buffer == NULL) {
348                         for (i = 0; i < hdr->stacklen; i++)
349                                 EC_LOG(EC_LOG_ERR, "  %p\n", hdr->stack[i]);
350                 } else {
351                         for (i = 0; i < hdr->stacklen; i++)
352                                 EC_LOG(EC_LOG_ERR, "  %s\n",
353                                         buffer ? buffer[i] : "unknown");
354                 }
355                 free(buffer);
356         }
357
358         EC_LOG(EC_LOG_ERR,
359                 "  missing static syms, use: addr2line -f -e <prog> <addr>\n");
360
361         return -1;
362 }
363
364 static int debug_log(int type, unsigned int level, void *opaque,
365                 const char *str)
366 {
367         (void)type;
368         (void)opaque;
369
370         if (level > (unsigned int)log_level)
371                 return 0;
372
373         if (printf("%s", str) < 0)
374                 return -1;
375
376         return 0;
377 }
378
379 int main(int argc, char **argv)
380 {
381         int i, ret = 0, leaks;
382
383         ret = parse_args(argc, argv);
384         if (ret < 0)
385                 return 1;
386
387         argc -= ret;
388         argv += ret;
389
390         srandom(seed);
391
392         /* register a new malloc to track memleaks */
393         TAILQ_INIT(&debug_alloc_hdr_list);
394         if (ec_malloc_register(debug_malloc, debug_free, debug_realloc) < 0) {
395                 EC_LOG(EC_LOG_ERR, "cannot register new malloc\n");
396                 return 1;
397         }
398
399         if (ec_init() < 0) {
400                 fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno));
401                 return 1;
402         }
403         ec_log_fct_register(debug_log, NULL);
404
405         ret = 0;
406         if (argc <= 1) {
407                 ret = ec_test_all();
408         } else {
409                 for (i = 1; i < argc; i++)
410                         ret |= ec_test_one(argv[i]);
411         }
412
413         leaks = debug_alloc_dump_leaks();
414
415         if (alloc_fail_proba == 0 && ret != 0) {
416                 printf("tests failed\n");
417                 return 1;
418         } else if (alloc_fail_proba != 0 && leaks != 0) {
419                 printf("tests failed (memory leak)\n");
420                 return 1;
421         }
422
423         printf("\ntests ok\n");
424
425         return 0;
426 }