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