collect-symbols: default to mrp_/_mrp prefix.
[profile/ivi/murphy.git] / utils / collect-symbols.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <stdarg.h>
5 #include <errno.h>
6 #define _GNU_SOURCE
7 #include <getopt.h>
8 #include <unistd.h>
9 #include <regex.h>
10
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <sys/wait.h>
14
15 #define RD 0
16 #define WR 1
17
18 typedef enum {
19     TOKEN_ERROR = -1,
20     TOKEN_LINEMARKER,                    /* a preprocessor line marker */
21     TOKEN_BLOCK,                         /* a block enclosed in {}/()/[] */
22     TOKEN_WORD,                          /* a word */
23     TOKEN_DQUOTED,                       /* a double-quoted sequence */
24     TOKEN_SQUOTED,                       /* a single-quoted sequence */
25     TOKEN_ASSIGN,                        /* '=' */
26     TOKEN_SEMICOLON,                     /* ';' */
27     TOKEN_COLON,                         /* ',' */
28     TOKEN_OTHER,                         /* any other token */
29 } token_type_t;
30
31
32 typedef struct {
33     token_type_t  type;                  /* token type */
34     char         *value;                 /* token value */
35 } token_t;
36
37
38 #define READBUF_SIZE ( 8 * 1024)
39 #define RINGBUF_SIZE (16 * 1024)
40 #define MAX_TOKEN    (512)
41 #define MAX_TOKENS   (64)
42
43 typedef struct {
44     int  fd;                             /* file descriptor to read */
45     char buf[READBUF_SIZE];              /* data buffer */
46     int  len;                            /* amount of data in buffer */
47     int  rd;                             /* data buffer read offset */
48     int  nxt;                            /* pushed back data if non-zero */
49 } input_t;
50
51 typedef struct {
52     char buf[RINGBUF_SIZE];              /* data buffer */
53     int  wr;                             /* write offset */
54 } ringbuf_t;
55
56 typedef struct {
57     char    *pattern;                    /* symbol pattern */
58     char   **files;                      /* files to parse for symbols */
59     int      nfile;                      /* number of files */
60     char    *cflags;                     /* compiler flags */
61     char    *output;                     /* output path */
62     int      gnuld;                      /* generate GNU ld script */
63     int      verbose;                    /* verbosity */
64 } config_t;
65
66 typedef struct {
67     char **syms;
68     int    nsym;
69 } symtab_t;
70
71
72 static int verbosity = 0;
73
74
75 static void fatal_error(const char *fmt, ...)
76 {
77     va_list ap;
78
79     va_start(ap, fmt);
80     vfprintf(stderr, fmt, ap);
81     va_end(ap);
82
83     exit(1);
84 }
85
86
87 static void verbose_message(int level, const char *fmt, ...)
88 {
89     va_list ap;
90
91     if (verbosity >= level) {
92         va_start(ap, fmt);
93         vfprintf(stderr, fmt, ap);
94         va_end(ap);
95     }
96 }
97
98
99 static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
100 {
101     va_list ap;
102
103     if (fmt && *fmt) {
104         va_start(ap, fmt);
105         vprintf(fmt, ap);
106         va_end(ap);
107     }
108
109     printf("usage: %s [options]\n\n"
110            "The possible options are:\n"
111            "  -c, --compiler-flags <flags> flags to pass to compiler\n"
112            "  -p, --pattern <pattern>      symbol regexp pattern\n"
113            "  -o, --output <path>          write output to the given file\n"
114            "  -g, --gnu-ld <script>        generate GNU ld linker script\n"
115            "  -v, --verbose                run in verbose mode\n"
116            "  -h, --help                   show this help on usage\n",
117            argv0);
118
119     if (exit_code < 0)
120         return;
121     else
122         exit(exit_code);
123 }
124
125
126 static void set_defaults(config_t *c)
127 {
128     memset(c, 0, sizeof(*c));
129     c->pattern = "^mrp_|^_mrp";
130 }
131
132
133 static void parse_cmdline(config_t *cfg, int argc, char **argv)
134 {
135 #   define OPTIONS "c:p:o:gvh"
136     struct option options[] = {
137         { "compiler-flags", required_argument, NULL, 'c' },
138         { "pattern"       , required_argument, NULL, 'p' },
139         { "output"        , required_argument, NULL, 'o' },
140         { "gnu-ld"        , no_argument      , NULL, 'g' },
141         { "verbose"       , no_argument      , NULL, 'v' },
142         { "help"          , no_argument      , NULL, 'h' },
143         { NULL, 0, NULL, 0 }
144     };
145
146     int opt;
147
148     set_defaults(cfg);
149
150     while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
151         switch (opt) {
152         case 'c':
153             cfg->cflags = optarg;
154             break;
155
156         case 'p':
157             cfg->pattern = optarg;
158             break;
159
160         case 'o':
161             cfg->output = optarg;
162             break;
163
164         case 'g':
165             cfg->gnuld = 1;
166             break;
167
168         case 'v':
169             verbosity++;
170             break;
171
172         case 'h':
173             print_usage(argv[0], -1, "");
174             exit(0);
175             break;
176
177         default:
178             print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
179         }
180     }
181
182     cfg->files = argv + optind;
183     cfg->nfile = argc - optind;
184 }
185
186
187 static int preprocess_file(const char *file, const char *cflags, pid_t *pid)
188 {
189     char cmd[4096], *argv[32];
190     int  fd[2], argc;
191
192     /*
193      * preprocess the given file
194      *
195      * Fork off a process for preprocessing the given file with the
196      * configured compiler flags. Return the reading end of the pipe
197      * the preprocessor is writing to.
198      */
199
200     if (pipe(fd) != 0)
201         fatal_error("failed to create pipe (%d: %s).", errno, strerror(errno));
202
203     *pid = fork();
204
205     switch (*pid) {
206     case -1:
207         fatal_error("failed to for preprocessor (%d: %s).",
208                     errno, strerror(errno));
209         break;
210
211     case 0: /* child: exec preprocessor */
212         close(fd[RD]);
213
214         argc         = 0;
215         argv[argc++] = "/bin/sh";
216         argv[argc++] = "-c";
217
218         if (cflags != NULL)
219             snprintf(cmd, sizeof(cmd), "gcc %s -E %s", cflags, file);
220         else
221             snprintf(cmd, sizeof(cmd), "gcc -E %s", file);
222
223         argv[argc++] = cmd;
224         argv[argc]   = NULL;
225
226         if (dup2(fd[WR], fileno(stdout)) < 0)
227             fatal_error("failed to redirect stdout (%d: %s)",
228                         errno, strerror(errno));
229
230         if (execv("/bin/sh", argv) != 0)
231             fatal_error("failed to exec command '%s' (%d: %s)", cmd,
232                         errno, strerror(errno));
233         break;
234
235     default: /* parent: return fd to read preprocessed data from */
236         close(fd[WR]);
237         return fd[RD];
238     }
239
240     return -1;  /* never reached */
241 }
242
243
244 static void input_init(input_t *in, int fd)
245 {
246     memset(in, 0, sizeof(*in));
247
248     in->fd = fd;
249 }
250
251
252 static char input_read(input_t *in)
253 {
254     char ch;
255
256     /*
257      * read the next input character
258      *
259      * If there is an pushed back character deliver (and clear) than one.
260      * Otherwise refill the input buffer if needed and return the next
261      * character from it.
262      */
263
264     if (in->nxt != 0) {
265         ch = in->nxt;
266         in->nxt = 0;
267     }
268     else {
269         if (in->len <= in->rd) {
270             in->len = read(in->fd, in->buf, sizeof(in->buf));
271
272             if (in->len > 0) {
273                 in->rd = 1;
274                 ch = in->buf[0];
275             }
276             else
277                 ch = 0;
278         }
279         else
280             return ch = in->buf[in->rd++];
281     }
282
283     return ch;
284 }
285
286
287 static int input_pushback(input_t *in, char ch)
288 {
289     /*
290      * push back a character to the input stream
291      *
292      * Note that you can only push back a single character. Trying to
293      * push back more than one will fail with an error.
294      */
295
296     if (in->nxt == 0) {
297         in->nxt = ch;
298
299         return 0;
300     }
301     else {
302         errno = EBUSY;
303
304         return -1;
305     }
306 }
307
308
309 static void input_discard_whitespace(input_t *in)
310 {
311     char ch;
312
313     /*
314      * discard consecutive whitespace (including newline)
315      */
316
317     while ((ch = input_read(in)) == ' ' || ch == '\t' || ch == '\n')
318         ;
319
320     input_pushback(in, ch);
321 }
322
323
324 #if 0
325 static void input_discard_line(input_t *in)
326 {
327     int ch;
328
329     /*
330      * discard input till a newline
331      */
332
333     while ((ch = input_read(in)) != '\n' && ch != 0)
334         ;
335 }
336 #endif
337
338
339 static int input_discard_quoted(input_t *in, char quote)
340 {
341     char ch;
342
343     /*
344      * discard a block of quoted input
345      */
346
347     while ((ch = input_read(in)) != quote && ch != 0) {
348         if (ch == '\\')
349             input_read(in);
350     }
351
352     if (ch != quote) {
353         errno = EINVAL;
354         return -1;
355     }
356     else
357         return 0;
358 }
359
360
361 static int input_discard_block(input_t *in, char beg)
362 {
363     char end, ch, quote;
364     int  level;
365
366     /*
367      * discard a block enclosed in {}, [], or ()
368      */
369
370     switch (beg) {
371     case '{': end = '}'; break;
372     case '[': end = ']'; break;
373     case '(': end = ')'; break;
374     default:             return 0;
375     }
376
377     level = 1;
378     while (level > 0) {
379         switch ((ch = input_read(in))) {
380         case '"':
381         case '\'':
382             quote = ch;
383             if (input_discard_quoted(in, quote) != 0)
384                 return -1;
385             break;
386
387         default:
388             if (ch == end)
389                 level--;
390             else if (ch == beg)
391                 level++;
392         }
393     }
394
395     if (level == 0)
396         return 0;
397     else {
398         errno = EINVAL;
399         return -1;
400     }
401 }
402
403
404 static void ringbuf_init(ringbuf_t *rb)
405 {
406     memset(rb->buf, 0, sizeof(rb->buf));
407     rb->wr = 0;
408 }
409
410
411 static char *ringbuf_save(ringbuf_t *rb, char *token, int len)
412 {
413     char *t, *s, *d;
414     int   n, o, i;
415
416     /*
417      * save the given token in the token ring buffer
418      */
419
420     verbose_message(2, "saving '%s'...\n", token);
421
422     if (len < 0)
423         len = strlen(token);
424
425     n = sizeof(rb->buf) - 1 - rb->wr;
426
427     if (n < len + 1) {
428         t = rb->buf;
429         n = sizeof(rb->buf) - 1;
430         o = 0;
431     }
432     else {
433         t = rb->buf + rb->wr;
434         o = rb->wr;
435     }
436
437     if (n >= len + 1) {
438         s = token;
439         d = t;
440
441         for (i = 0; i < len; i++, o++)
442             *d++ = *s++;
443
444         *d = '\0';
445         rb->wr = o + 1;
446
447         return t;
448     }
449     else {
450         errno = ENOSPC;
451         return NULL;
452     }
453 }
454
455
456 static char *input_collect_word(input_t *in, ringbuf_t *rb)
457 {
458 #define WORD_CHAR(c)                            \
459     (('a' <= (c) && (c) <= 'z') ||              \
460      ('A' <= (c) && (c) <= 'Z') ||              \
461      ('0' <= (c) && (c) <= '9') ||              \
462      ((c) == '_' || (c) == '$'))
463
464     char buf[MAX_TOKEN], ch;
465     int  n;
466
467     /*
468      * collect and save the next word (consecutive sequence) of input
469      */
470
471     for (n = 0; n < (int)sizeof(buf) - 1; n++) {
472         ch = input_read(in);
473
474         if (WORD_CHAR(ch))
475             buf[n] = ch;
476         else {
477             buf[n] = '\0';
478             input_pushback(in, ch);
479
480             return ringbuf_save(rb, buf, n);
481         }
482     }
483
484     errno = ENOSPC;
485     return NULL;
486 }
487
488
489 static char *input_parse_linemarker(input_t *in, char *buf, size_t size)
490 {
491     char ch;
492     int  i;
493
494     while((ch = input_read(in)) != '"' && ch != '\n' && ch)
495         ;
496
497     if (ch != '"')
498         return NULL;
499
500     for (i = 0; i < (int)size - 1; i++) {
501         buf[i] = ch = input_read(in);
502
503         if (ch == '"') {
504             buf[i] = '\0';
505
506             while ((ch = input_read(in)) != '\n' && ch)
507                 ;
508
509             return buf;
510         }
511     }
512
513     return NULL;
514 }
515
516
517 static int same_file(const char *path1, const char *path2)
518 {
519     struct stat st1, st2;
520
521     if (stat(path1, &st1) != 0 || stat(path2, &st2) != 0)
522         return 0;
523     else
524         return st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino;
525 }
526
527
528 static int collect_tokens(input_t *in, ringbuf_t *rb, token_t *tokens,
529                           int ntoken)
530 {
531     char ch, *v, path[1024];
532     int  n, has_paren;
533
534     /*
535      * collect a sequence of tokens that forms (or looks like) a logical unit
536      */
537
538     n = 0;
539     has_paren = 0;
540     while (n < ntoken) {
541         switch ((ch = input_read(in))) {
542             /* always treat a semicolon here as a sequence terminator */
543         case ';':
544             tokens[n].type  = TOKEN_SEMICOLON;
545             tokens[n].value = ringbuf_save(rb, ";", 1);
546             return n + 1;
547
548             /* extract path name from preprocessor line-markers */
549         case '#':
550             v = input_parse_linemarker(in, path, sizeof(path));
551             if (v != NULL) {
552                 tokens[n].type  = TOKEN_LINEMARKER;
553                 tokens[n].value = ringbuf_save(rb, v, -1);
554                 if (n == 0)
555                     return n + 1;
556                 else
557                     return -1;
558             }
559             break;
560
561             /* discard whitespace (including trailing newlines) */
562         case ' ':
563         case '\t':
564             input_discard_whitespace(in);
565             break;
566
567             /* ignore newlines */
568         case '\n':
569             break;
570
571             /* collate/collapse blocks to a block indicator token */
572         case '{':
573         case '(':
574         case '[':
575             if (input_discard_block(in, ch) != 0)
576                 return -1;
577             else {
578                 /* filter out __attribute__ ((.*)) token pairs */
579                 if (ch == '(' && n > 0 &&
580                     tokens[n-1].type == TOKEN_WORD &&
581                     !strcmp(tokens[n-1].value, "__attribute__")) {
582                     n--;
583                     verbose_message(2, "filtered __attribute__...\n");
584                     continue;
585                 }
586
587                 v = (ch == '{' ? "{" : (ch == '[' ? "[" : "("));
588                 tokens[n].type  = TOKEN_BLOCK;
589                 tokens[n].value = ringbuf_save(rb, v, 1);
590                 n++;
591
592                 if (v[0] == '(')
593                     has_paren = 1;
594                 else {
595                     /*
596                      * if this sequence includes both '(...)' and '{...}'
597                      * we assume this to be a function definition so we
598                      * don't wait for a semicolon but terminate sequence
599                      * here
600                      */
601                     if (v[0] == '{')
602                         if (has_paren)
603                             return n;
604                 }
605             }
606             break;
607
608             /* end of file terminates the current sequence */
609         case 0:
610             return n;
611
612             /* collect and save the next word */
613         case 'a'...'z':
614         case 'A'...'Z':
615         case '_':
616         case '$':
617         case '0'...'9':
618             input_pushback(in, ch);
619             v = input_collect_word(in, rb);
620
621             if (v != NULL) {
622                 if (!strcmp(v, "__extension__"))
623                     break;
624                 tokens[n].type  = TOKEN_WORD;
625                 tokens[n].value = v;
626                 n++;
627             }
628             else
629                 return -1;
630             break;
631
632         case '=':
633             tokens[n].type  = TOKEN_ASSIGN;
634             tokens[n].value = ringbuf_save(rb, "=", 1);
635             n++;
636             break;
637
638             /* ignore asterisks */
639         case '*':
640             break;
641
642             /* the rest we print for debugging */
643         default:
644             printf("%c", ch);
645         }
646     }
647
648     errno = EOVERFLOW;
649     return -1;
650 }
651
652
653 static char *symbol_from_tokens(token_t *tokens, int ntoken)
654 {
655 #define MATCHING_TOKEN(_n, _type, _val)                         \
656     (tokens[(_n)].type == TOKEN_##_type &&                      \
657      (!*_val || !strcmp(_val, tokens[(_n)].value)))
658
659     int last, has_paren, has_curly, has_bracket, has_assign;
660     int i;
661
662     /*
663      * extract the symbol from a sequence of tokens
664      */
665
666     if (verbosity > 2) {
667         for (i = 0; i < ntoken; i++)
668             verbose_message(3, "0x%x: '%s'\n", tokens[i].type, tokens[i].value);
669         verbose_message(3, "--\n");
670     }
671
672     has_paren = has_curly = has_bracket = has_assign = 0;
673     for (i = 0; i < ntoken; i++) {
674         if      (MATCHING_TOKEN(i, BLOCK , "(")) has_paren   = 1;
675         else if (MATCHING_TOKEN(i, BLOCK , "{")) has_curly   = 1;
676         else if (MATCHING_TOKEN(i, BLOCK , "[")) has_bracket = 1;
677         else if (MATCHING_TOKEN(i, ASSIGN, "" )) has_assign  = 1 + i;
678     }
679
680     last = ntoken - 1;
681
682     if (tokens[0].type != TOKEN_WORD) {
683         verbose_message(2, "ignoring sequence starting with non-word\n");
684         return NULL;
685     }
686
687     /* ignore typedefs and everything static */
688     if (MATCHING_TOKEN(0, WORD, "typedef") ||
689         MATCHING_TOKEN(0, WORD, "static")) {
690         verbose_message(2, "ignoring typedef or static sequence\n");
691         return NULL;
692     }
693
694     /* ignore forward declarations */
695     if (ntoken == 3 &&
696         (MATCHING_TOKEN(0, WORD, "struct") ||
697          MATCHING_TOKEN(0, WORD, "union" ) ||
698          MATCHING_TOKEN(0, WORD, "enum"  )) &&
699         MATCHING_TOKEN(1, WORD, "") &&
700         MATCHING_TOKEN(2, SEMICOLON, "")) {
701         verbose_message(2, "ignoring forward declaration sequence\n");
702         return NULL;
703     }
704
705     /* take care of function prototypes */
706     if (last > 2) {
707         if (MATCHING_TOKEN(last  , SEMICOLON, "" ) &&
708             MATCHING_TOKEN(last-1, BLOCK    , "(") &&
709             MATCHING_TOKEN(last-2, WORD     , "" ))
710             return tokens[last-2].value;
711     }
712
713     /* take care of global variables with assignments */
714     if (last > 1 && has_assign) {
715         i = has_assign - 1;
716         if (i > 0 && MATCHING_TOKEN(i-1, WORD, ""))
717             return tokens[i-1].value;
718         if (i > 1 &&
719             MATCHING_TOKEN(i-1, BLOCK, "[") &&
720             MATCHING_TOKEN(i-2, WORD , ""))
721             return tokens[i-2].value;
722     }
723
724     /* take care of global variables */
725     if (last > 1 && !has_paren && !has_curly) {
726         if (MATCHING_TOKEN(last  , SEMICOLON, "") &&
727             MATCHING_TOKEN(last-1, WORD     , ""))
728             return tokens[last-1].value;
729     }
730
731     verbose_message(2, "ignoring other non-matching token sequence\n");
732
733     return NULL;
734 }
735
736
737 static void symtab_init(symtab_t *st)
738 {
739     st->syms = NULL;
740     st->nsym = 0;
741 }
742
743
744 static void symtab_add(symtab_t *st, char *sym)
745 {
746     int i;
747
748     for (i = 0; i < st->nsym; i++)
749         if (!strcmp(st->syms[i], sym))
750             return;
751
752     st->syms = realloc(st->syms, (st->nsym + 1) * sizeof(st->syms[0]));
753
754     if (st->syms != NULL) {
755         st->syms[st->nsym] = strdup(sym);
756
757         if (st->syms[st->nsym] != NULL) {
758             st->nsym++;
759
760             return;
761         }
762
763         fatal_error("failed to save symbol '%s'", sym);
764     }
765
766     fatal_error("failed to allocate new symbol table entry");
767 }
768
769
770 static void symtab_reset(symtab_t *st)
771 {
772     int i;
773
774     for (i = 0; i < st->nsym; i++)
775         free(st->syms[i]);
776
777     free(st->syms);
778
779     st->syms = NULL;
780     st->nsym = 0;
781 }
782
783
784 static void symtab_dump(symtab_t *st, int gnuld, FILE *out)
785 {
786     int i;
787
788     if (!gnuld) {
789         for (i = 0; i < st->nsym; i++)
790             fprintf(out, "%s\n", st->syms[i]);
791     }
792     else {
793         fprintf(out, "{\n");
794         if (st->nsym > 0) {
795             fprintf(out, "    global:\n");
796             for (i = 0; i < st->nsym; i++)
797                 fprintf(out, "        %s;\n", st->syms[i]);
798         }
799         fprintf(out, "    local:\n");
800         fprintf(out, "        *;\n");
801         fprintf(out, "};\n");
802     }
803 }
804
805
806 static void extract_symbols(const char *path, const char *cflags,
807                             symtab_t *st, regex_t *re)
808 {
809     input_t   in;
810     ringbuf_t rb;
811     int       fd;
812     pid_t     pp_pid;
813     token_t   tokens[MAX_TOKENS];
814     int       ntoken;
815     char     *sym;
816     int       pp_status, foreign;
817
818     fd = preprocess_file(path, cflags, &pp_pid);
819
820     input_init(&in, fd);
821     ringbuf_init(&rb);
822
823     while ((ntoken = collect_tokens(&in, &rb, tokens, MAX_TOKENS)) > 0) {
824         if (tokens[0].type == TOKEN_LINEMARKER) {
825             foreign = !same_file(path, tokens[0].value);
826
827             verbose_message(1, "input switched to %s file '%s'...",
828                             foreign ? "foreign" : "input", tokens[0].value);
829
830             continue;
831         }
832
833         if (foreign) {
834             verbose_message(2, "ignoring token stream from foreign file...\n");
835             continue;
836         }
837
838         sym = symbol_from_tokens(tokens, ntoken);
839
840         if (sym != NULL) {
841             if (re == NULL || regexec(re, sym, 0, NULL, 0) == 0)
842                 symtab_add(st, sym);
843             else
844                 verbose_message(1, "filtered non-matching '%s'...\n", sym);
845         }
846     }
847
848     close(fd);
849     waitpid(pp_pid, &pp_status, 0);
850
851     if (WIFEXITED(pp_status) && WEXITSTATUS(pp_status) != 0)
852         fatal_error("preprocessing of '%s' failed\n", path);
853 }
854
855
856 int main(int argc, char *argv[])
857 {
858     config_t  cfg;
859     symtab_t  st;
860     regex_t   rebuf, *re;
861     char      regerr[1024];
862     FILE     *out;
863     int       err, i;
864
865     symtab_init(&st);
866     parse_cmdline(&cfg, argc, argv);
867
868     if (cfg.pattern != NULL) {
869         err = regcomp(&rebuf, cfg.pattern, REG_EXTENDED);
870
871         if (err != 0) {
872             regerror(err, &rebuf, regerr, sizeof(regerr));
873             fatal_error("invalid pattern '%s' (error: %s)\n", cfg.pattern,
874                         regerr);
875         }
876
877         re = &rebuf;
878     }
879     else
880         re = NULL;
881
882     for (i = 0; i < cfg.nfile; i++)
883         extract_symbols(cfg.files[i], cfg.cflags, &st, re);
884
885     if (cfg.output != NULL) {
886         out = fopen(cfg.output, "w");
887
888         if (out == NULL)
889             fatal_error("failed to open '%s' (%d: %s)", cfg.output,
890                         errno, strerror(errno));
891     }
892     else
893         out = stdout;
894
895     symtab_dump(&st, cfg.gnuld, out);
896
897     if (re != NULL)
898         regfree(re);
899
900     symtab_reset(&st);
901
902     if (out != stdout)
903         fclose(out);
904
905     return 0;
906 }