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