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