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