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