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