Added namespacize, to prefix exposed symbols with CCAN_/ccan_.
[ccan] / ccan_tools / namespacize.c
1 /* Code to move a ccan module into the ccan_ namespace. */
2 #include <err.h>
3 #include <errno.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <sys/types.h>
7 #include <sys/stat.h>
8 #include <fcntl.h>
9 #include <stdbool.h>
10 #include <ctype.h>
11 #include <sys/types.h>
12 #include <dirent.h>
13 #include "talloc/talloc.h"
14
15 #define CFLAGS "-O3 -Wall -Wundef -Wstrict-prototypes -Wold-style-definition -Wmissing-prototypes -Wmissing-declarations -Werror -I. -Iccan_tools/libtap/src/"
16 #define CFLAGS_HDR "-Wall -Wundef -Wstrict-prototypes -Wold-style-definition -Werror -I."
17
18 static bool verbose = false;
19 static int indent = 0;
20 #define verbose(args...)                                                \
21         do { if (verbose) {                                             \
22                         unsigned int _i;                                \
23                         for (_i = 0; _i < indent; _i++) printf(" ");    \
24                         printf(args);                                   \
25                 }                                                       \
26         } while(0)
27 #define verbose_indent() (indent += 2)
28 #define verbose_unindent() (indent -= 2)
29
30 #define streq(a,b) (strcmp((a),(b)) == 0)
31
32 #define strstarts(str,prefix) (strncmp((str),(prefix),strlen(prefix)) == 0)
33
34 static inline bool strends(const char *str, const char *postfix)
35 {
36         if (strlen(str) < strlen(postfix))
37                 return false;
38
39         return streq(str + strlen(str) - strlen(postfix), postfix);
40 }
41
42 static int close_no_errno(int fd)
43 {
44         int ret = 0, serrno = errno;
45         if (close(fd) < 0)
46                 ret = errno;
47         errno = serrno;
48         return ret;
49 }
50
51 static int unlink_no_errno(const char *filename)
52 {
53         int ret = 0, serrno = errno;
54         if (unlink(filename) < 0)
55                 ret = errno;
56         errno = serrno;
57         return ret;
58 }
59
60 static void *grab_fd(const void *ctx, int fd)
61 {
62         int ret;
63         unsigned int max = 16384, size = 0;
64         char *buffer;
65
66         buffer = talloc_array(ctx, char, max+1);
67         while ((ret = read(fd, buffer + size, max - size)) > 0) {
68                 size += ret;
69                 if (size == max)
70                         buffer = talloc_realloc(ctx, buffer, char, max*=2 + 1);
71         }
72         if (ret < 0) {
73                 talloc_free(buffer);
74                 buffer = NULL;
75         } else
76                 buffer[size] = '\0';
77
78         return buffer;
79 }
80
81 /* This version adds one byte (for nul term) */
82 static void *grab_file(const void *ctx, const char *filename)
83 {
84         int fd;
85         char *buffer;
86
87         if (streq(filename, "-"))
88                 fd = dup(STDIN_FILENO);
89         else
90                 fd = open(filename, O_RDONLY, 0);
91
92         if (fd < 0)
93                 return NULL;
94
95         buffer = grab_fd(ctx, fd);
96         close_no_errno(fd);
97         return buffer;
98 }
99
100 /* This is a dumb one which copies.  We could mangle instead. */
101 static char **split(const void *ctx, const char *text, const char *delims,
102                     unsigned int *nump)
103 {
104         char **lines = NULL;
105         unsigned int max = 64, num = 0;
106
107         lines = talloc_array(ctx, char *, max+1);
108
109         while (*text != '\0') {
110                 unsigned int len = strcspn(text, delims);
111                 lines[num] = talloc_array(lines, char, len + 1);
112                 memcpy(lines[num], text, len);
113                 lines[num][len] = '\0';
114                 text += len;
115                 text += strspn(text, delims);
116                 if (++num == max)
117                         lines = talloc_realloc(ctx, lines, char *, max*=2 + 1);
118         }
119         lines[num] = NULL;
120         if (nump)
121                 *nump = num;
122         return lines;
123 }
124
125 static char **get_dir(const char *dir)
126 {
127         DIR *d;
128         struct dirent *ent;
129         char **names = NULL;
130         unsigned int size = 0;
131
132         d = opendir(dir);
133         if (!d)
134                 return NULL;
135
136         while ((ent = readdir(d)) != NULL) {
137                 names = talloc_realloc(dir, names, char *, size + 2);
138                 names[size++]
139                         = talloc_asprintf(names, "%s/%s", dir, ent->d_name);
140         }
141         names[size++] = NULL;
142         closedir(d);
143         return names;
144 }
145
146 static char ** __attribute__((format(printf, 2, 3)))
147 lines_from_cmd(const void *ctx, char *format, ...)
148 {
149         va_list ap;
150         char *cmd, *buffer;
151         FILE *p;
152
153         va_start(ap, format);
154         cmd = talloc_vasprintf(ctx, format, ap);
155         va_end(ap);
156
157         p = popen(cmd, "r");
158         if (!p)
159                 err(1, "Executing '%s'", cmd);
160
161         buffer = grab_fd(ctx, fileno(p));
162         if (!buffer)
163                 err(1, "Reading from '%s'", cmd);
164         pclose(p);
165
166         return split(ctx, buffer, "\n", NULL);
167 }
168
169 static char *build_obj(const char *cfile)
170 {
171         char *cmd;
172         char *ofile = talloc_strdup(cfile, cfile);
173
174         ofile[strlen(ofile)-1] = 'c';
175
176         cmd = talloc_asprintf(ofile, "gcc " CFLAGS " -o %s -c %s",
177                               ofile, cfile);
178         if (system(cmd) != 0)
179                 errx(1, "Failed to compile %s", cfile);
180         return ofile;
181 }
182
183 struct replace
184 {
185         struct replace *next;
186         char *string;
187 };
188
189 static void __attribute__((noreturn)) usage(void)
190 {
191         errx(1, "Usage:\n"
192              "namespacize [--verbose] <dir>\n"
193              "namespacize [--verbose] --adjust <dir>...\n"
194              "The first form converts dir/ to insert 'ccan_' prefixes, and\n"
195              "then adjusts any other ccan directories at the same level which\n"
196              "are effected.\n"
197              "--adjust does an adjustment for each directory, in case a\n"
198              "dependency has been namespacized\n");
199 }
200
201 static void add_replace(struct replace **repl, const char *str)
202 {
203         struct replace *new;
204
205         /* Don't replace things already CCAN-ized (eg. idempotent wrappers) */
206         if (strstarts(str, "CCAN_") || strstarts(str, "ccan_"))
207                 return;
208
209         new = talloc(*repl, struct replace);
210         new->next = *repl;
211         new->string = talloc_strdup(new, str);
212         *repl = new;
213 }
214
215 static char *basename(const void *ctx, const char *dir)
216 {
217         char *p = strrchr(dir, '/');
218
219         if (!p)
220                 return (char *)dir;
221         return talloc_strdup(ctx, p+1);
222 }
223
224 /* FIXME: Only does main header, should chase local includes. */ 
225 static void analyze_headers(const char *dir, struct replace **repl)
226 {
227         char *hdr, *contents, *p;
228         enum { LINESTART, HASH, DEFINE, NONE } state = LINESTART;
229
230         /* Get hold of header, assume that's it. */
231         hdr = talloc_asprintf(dir, "%s/%s.h", dir, basename(dir, dir));
232         contents = grab_file(dir, hdr);
233         if (!contents)
234                 err(1, "Reading %s", hdr);
235
236         verbose("Looking in %s\n", hdr);
237         verbose_indent();
238         /* Look for lines of form #define X */
239         for (p = contents; *p; p++) {
240                 if (*p == '\n')
241                         state = LINESTART;
242                 else if (!isspace(*p)) {
243                         if (state == LINESTART && *p == '#')
244                                 state = HASH;
245                         else if (state==HASH && !strncmp(p, "define", 6)) {
246                                 state = DEFINE;
247                                 p += 5;
248                         } else if (state == DEFINE) {
249                                 unsigned int len;
250
251                                 len = strspn(p, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
252                                              "abcdefghijklmnopqrstuvwxyz"
253                                              "01234567889_");
254                                 if (len) {
255                                         char *s;
256                                         s = talloc_strndup(contents, p, len);
257                                         verbose("Found %s\n", s);
258                                         add_replace(repl, s);
259                                 }
260                                 state = NONE;
261                         } else
262                                 state = NONE;
263                 }
264         }
265         verbose_unindent();
266 }
267
268 static void add_extern_symbols(const char *ofile, struct replace **repl)
269 {
270         /* Should actually read the elf: this is a hack. */
271         char **line;
272
273         line = lines_from_cmd(ofile, "nm --defined-only --extern %s", ofile);
274
275         /* nm output is of form [hexaddr] [char] [name]\n */
276         for (; *line; line++) {
277                 unsigned int cols;
278                 char **names = split(ofile, *line, " \t", &cols);
279                 if (cols != 3)
280                         errx(1, "Unexpected nm line '%s' (%i cols)", *line, cols);
281
282                 verbose("Found %s\n", names[2]);
283                 add_replace(repl, names[2]);
284         }
285 }
286
287 static void get_header_symbols(const char *dir, struct replace **repl)
288 {
289         char *cmd;
290         char *hfile = talloc_asprintf(dir, "%s/%s.h", dir, basename(dir, dir));
291         char *ofile = talloc_asprintf(dir, "%s.o", hfile);
292
293         /* Horrible hack to get static inlines. */
294         cmd = talloc_asprintf(dir, "gcc " CFLAGS_HDR
295                               " -Dstatic= -include %s -o %s -c -x c /dev/null",
296                               hfile, ofile);
297         if (system(cmd) != 0)
298                 errx(1, "Failed to compile %s", hfile);
299
300         add_extern_symbols(ofile, repl);
301 }
302
303 /* FIXME: Better to analyse headers in more depth, rather than recompile. */
304 static void get_exposed_symbols(const char *dir, struct replace **repl)
305 {
306         char **files;
307         unsigned int i;
308
309         files = get_dir(dir);
310         for (i = 0; files[i]; i++) {
311                 char *ofile;
312                 if (!strends(files[i], ".c") || strends(files[i], "/_info.c"))
313                         continue;
314
315                 /* This produces file.c -> file.o */
316                 ofile = build_obj(files[i]);
317                 verbose("Looking in %s\n", ofile);
318                 verbose_indent();
319                 add_extern_symbols(ofile, repl);
320                 unlink(ofile);
321                 verbose_unindent();
322         }
323         get_header_symbols(dir, repl);
324 }
325
326 static void write_replacement_file(const char *dir, struct replace **repl)
327 {
328         char *replname = talloc_asprintf(dir, "%s/.namespacize", dir);
329         int fd;
330         struct replace *r;
331
332         fd = open(replname, O_WRONLY|O_CREAT|O_EXCL, 0644);
333         if (fd < 0) {
334                 if (errno == EEXIST)
335                         errx(1, "%s already exists: can't namespacize twice",
336                              replname);
337                 err(1, "Opening %s", replname);
338         }
339
340         for (r = *repl; r; r = r->next) {
341                 if (write(fd,r->string,strlen(r->string)) != strlen(r->string)
342                     || write(fd, "\n", 1) != 1) {
343                         unlink_no_errno(replname);
344                         if (errno == 0)
345                                 errx(1, "Short write to %s: disk full?",
346                                      replname);
347                         errx(1, "Writing to %s", replname);
348                 }
349         }
350
351         close(fd);
352 }
353
354 static int unlink_destroy(char *name)
355 {
356         unlink(name);
357         return 0;
358 }
359
360 static char *find_word(char *f, const char *str)
361 {
362         char *p = f;
363
364         while ((p = strstr(p, str)) != NULL) {
365                 /* Check it's not in the middle of a word. */
366                 if (p > f && (isalnum(p[-1]) || p[-1] == '_')) {
367                         p++;
368                         continue;
369                 }
370                 if (isalnum(p[strlen(str)]) || p[strlen(str)] == '_') {
371                         p++;
372                         continue;
373                 }
374                 return p;
375         }
376         return NULL;
377 }
378
379 /* This is horribly inefficient but simple. */
380 static const char *rewrite_file(const char *filename,
381                                 const struct replace *repl)
382 {
383         char *newname, *file;
384         int fd;
385
386         verbose("Rewriting %s\n", filename);
387         file = grab_file(filename, filename);
388         if (!file)
389                 err(1, "Reading file %s", filename);
390
391         for (; repl; repl = repl->next) {
392                 char *p;
393
394                 while ((p = find_word(file, repl->string)) != NULL) {
395                         unsigned int off;
396                         char *new = talloc_array(file, char, strlen(file)+6);
397
398                         off = p - file;
399                         memcpy(new, file, off);
400                         if (isupper(repl->string[0]))
401                                 memcpy(new + off, "CCAN_", 5);
402                         else
403                                 memcpy(new + off, "ccan_", 5);
404                         strcpy(new + off + 5, file + off);
405                         file = new;
406                 }
407         }
408
409         /* If we exit for some reason, we want this erased. */
410         newname = talloc_asprintf(talloc_autofree_context(), "%s.tmp",
411                                   filename);
412         fd = open(newname, O_WRONLY|O_CREAT|O_EXCL, 0644);
413         if (fd < 0)
414                 err(1, "Creating %s", newname);
415
416         talloc_set_destructor(newname, unlink_destroy);
417         if (write(fd, file, strlen(file)) != strlen(file)) {
418                 if (errno == 0)
419                         errx(1, "Short write to %s: disk full?", newname);
420                 errx(1, "Writing to %s", newname);
421         }
422         close(fd);
423         return newname;
424 }
425
426 struct adjusted
427 {
428         struct adjusted *next;
429         const char *file;
430         const char *tmpfile;
431 };
432
433 static void setup_adjust_files(const char *dir,
434                                const struct replace *repl,
435                                struct adjusted **adj)
436 {
437         char **files;
438
439         for (files = get_dir(dir); *files; files++) {
440                 if (strends(*files, "/test"))
441                         setup_adjust_files(*files, repl, adj);
442                 else if (strends(*files, ".c") || strends(*files, ".h")) {
443                         struct adjusted *a = talloc(dir, struct adjusted);
444                         a->next = *adj;
445                         a->file = *files;
446                         a->tmpfile = rewrite_file(a->file, repl);
447                         *adj = a;
448                 }
449         }
450 }
451
452 /* This is the "commit" stage, so we hope it won't fail. */
453 static void rename_files(const struct adjusted *adj)
454 {
455         while (adj) {
456                 if (rename(adj->tmpfile, adj->file) != 0)
457                         warn("Could not rename over '%s', we're in trouble",
458                              adj->file);
459                 adj = adj->next;
460         }
461 }
462
463 static void convert_dir(const char *dir)
464 {
465         char *name;
466         struct replace *replace = NULL;
467         struct adjusted *adj = NULL;
468
469         /* Remove any ugly trailing slashes. */
470         name = talloc_strdup(NULL, dir);
471         while (strends(name, "/"))
472                 name[strlen(name)-1] = '\0';
473
474         analyze_headers(name, &replace);
475         get_exposed_symbols(name, &replace);
476         write_replacement_file(name, &replace);
477         setup_adjust_files(name, replace, &adj);
478         rename_files(adj);
479         talloc_free(name);
480         talloc_free(replace);
481 }
482
483 static struct replace *read_replacement_file(const char *depdir)
484 {
485         struct replace *repl = NULL;
486         char *replname = talloc_asprintf(depdir, "%s/.namespacize", depdir);
487         char *file, **line;
488
489         file = grab_file(replname, replname);
490         if (!file) {
491                 if (errno != ENOENT)
492                         err(1, "Opening %s", replname);
493                 return NULL;
494         }
495
496         for (line = split(file, file, "\n", NULL); *line; line++)
497                 add_replace(&repl, *line);
498         return repl;
499 }
500
501 static char *build_info(const void *ctx, const char *dir)
502 {
503         char *file, *cfile, *cmd;
504
505         cfile = talloc_asprintf(ctx, "%s/%s", dir, "_info.c");
506         file = talloc_asprintf(cfile, "%s/%s", dir, "_info");
507         cmd = talloc_asprintf(file, "gcc " CFLAGS " -o %s %s", file, cfile);
508         if (system(cmd) != 0)
509                 errx(1, "Failed to compile %s", file);
510
511         return file;
512 }
513
514 static char **get_deps(const void *ctx, const char *dir)
515 {
516         char **deps, *cmd;
517
518         cmd = talloc_asprintf(ctx, "%s depends", build_info(ctx, dir));
519         deps = lines_from_cmd(cmd, cmd);
520         if (!deps)
521                 err(1, "Could not run '%s'", cmd);
522         return deps;
523 }
524
525 static char *parent_dir(const void *ctx, const char *dir)
526 {
527         char *parent, *slash;
528
529         parent = talloc_strdup(ctx, dir);
530         slash = strrchr(parent, '/');
531         if (slash)
532                 *slash = '\0';
533         else
534                 parent = talloc_strdup(ctx, ".");
535         return parent;
536 }
537
538 static void adjust_dir(const char *dir)
539 {
540         char *parent = parent_dir(NULL, dir);
541         char **deps;
542
543         verbose("Adjusting %s\n", dir);
544         verbose_indent();
545         for (deps = get_deps(parent, dir); *deps; deps++) {
546                 char *depdir;
547                 struct adjusted *adj = NULL;
548                 struct replace *repl;
549
550                 depdir = talloc_asprintf(parent, "%s/%s", parent, *deps);
551                 repl = read_replacement_file(depdir);
552                 if (repl) {
553                         verbose("%s has been namespacized\n", depdir);
554                         setup_adjust_files(parent, repl, &adj);
555                         rename_files(adj);
556                 } else
557                         verbose("%s has not been namespacized\n", depdir);
558                 talloc_free(depdir);
559         }
560         verbose_unindent();
561 }
562
563 static void adjust_dependents(const char *dir)
564 {
565         char *parent = parent_dir(NULL, dir);
566         char *base = basename(parent, dir);
567         char **file;
568
569         verbose("Looking for dependents in %s\n", parent);
570         verbose_indent();
571         for (file = get_dir(parent); *file; file++) {
572                 char *infoc, **deps;
573                 bool isdep = false;
574
575                 if (basename(*file, *file)[0] == '.')
576                         continue;
577
578                 infoc = talloc_asprintf(*file, "%s/_info.c", *file);
579                 if (access(infoc, R_OK) != 0)
580                         continue;
581
582                 for (deps = get_deps(*file, *file); *deps; deps++) {
583                         if (streq(*deps, base))
584                                 isdep = true;
585                 }
586                 if (isdep)
587                         adjust_dir(*file);
588                 else
589                         verbose("%s is not dependent\n", *file);
590         }
591         verbose_unindent();
592 }
593
594 int main(int argc, char *argv[])
595 {
596         if (argv[1] && streq(argv[1], "--verbose")) {
597                 verbose = true;
598                 argv++;
599                 argc--;
600         }
601
602         if (argc == 2) {
603                 verbose("Namespacizing %s\n", argv[1]);
604                 verbose_indent();
605                 convert_dir(argv[1]);
606                 adjust_dependents(argv[1]);
607                 verbose_unindent();
608                 return 0;
609         }
610
611         if (argc > 2 && streq(argv[1], "--adjust")) {
612                 unsigned int i;
613
614                 for (i = 2; i < argc; i++)
615                         adjust_dir(argv[i]);
616                 return 0;
617         }
618         usage();
619 }