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