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