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