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