fix get_ioctl_names.sh
[moctel.git] / gen_method_list.c
1 #define _GNU_SOURCE
2
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>
6 #include <unistd.h>
7 #include <stdio.h>
8 #include <sys/mman.h>
9 #include <dirent.h>
10 #include <stdbool.h>
11 #include <assert.h>
12 #include <stdlib.h>
13 #include <string.h>
14
15 static int out_fd;
16 char outbuf[10000];
17 int outbuf_i = 0;
18
19 void clear_out(void) {
20   if (write(out_fd, outbuf, outbuf_i) != outbuf_i) {
21     fprintf(stderr, "write to outfile did not succeed\n");
22     exit(1);
23   }
24   outbuf_i = 0;
25 }
26
27 void write_out(char *buf) {
28   size_t len = strlen(buf);
29   assert(len <= 500);
30   memcpy(outbuf+outbuf_i, buf, len);
31   outbuf_i += len;
32   if (outbuf_i > 9500) clear_out();
33 }
34
35 /* Maps a textfile into RAM. Returns a pointer to a read-only memory
36    area containing the 0-terminated data. len includes the nullbyte.
37    Free it using munmap. */
38 char *map_textfile(char *path, size_t *len) {
39   int fd = open(path, O_RDONLY);
40   if (fd == -1) fprintf(stderr, "can't open %s: %m\n", path), exit(1);
41
42   struct stat st;
43   if (fstat(fd, &st)) fprintf(stderr, "can't stat %s: %m\n", path), exit(1);
44   *len = st.st_size;
45
46   char *data = mmap(NULL, *len+1, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
47   if (data == MAP_FAILED) fprintf(stderr, "can't mmap %s: %m\n", path), exit(1);
48
49   close(fd);
50
51   if ((*len & 4095) == 0) {
52     // TODO: is this ok? does the previous mmap make sure that that address exists?
53     void *nulls_addr = mmap(data+*len, 1, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
54     if (nulls_addr != data+*len)
55       fprintf(stderr, "the mmap trick for 0-termination failed :(\n"), exit(1);
56     *len += 1;
57   }
58
59   return data;
60 }
61
62 bool isichar(char c) {
63   return (c>='a'&&c<='z') || (c>='A'&&c<='Z') || (c>='0'&&c<='9') || c=='_';
64 }
65
66 void handle_file(char *name, char *path) {
67   size_t data_len;
68   char *data = map_textfile(name, &data_len);
69
70   bool wrote_path = false;
71
72   /* look for stuff that looks like a method definition. this means:
73    *  - line starts with a non-whitespace
74    *  - there is a token (the name) followed by an opening paren
75    *  - the next { is earlier than the next ;
76    */
77   char *s = data;
78   while (1) {
79     char *lineend = strchr(s, '\n');
80     if (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\0') goto next;
81     if (lineend) *lineend = '\0';
82     char *paren = strchr(s, '(');
83     if (paren == NULL) goto next;
84     *lineend = '\n';
85     char *next_curly = strchr(paren, '{');
86     if (next_curly == NULL) goto next;
87     char *next_semicolon = strchr(paren, ';');
88     if (next_semicolon != NULL && next_semicolon < next_curly)
89       goto next; /* declaration, not definition */
90     char *nameend = paren;
91     while (nameend>s+1 && !isichar(nameend[-1])) nameend--; /* let nameend point behind the name */
92     *nameend = '\0';
93     char *name = nameend-1;
94     while (name>s && isichar(name[-1])) name--;
95     if (!wrote_path) {
96       write_out(path);
97       write_out(":");
98       wrote_path = true;
99     } else {
100       write_out(",");
101     }
102     write_out(name);
103     
104 next:
105     if (!lineend) break;
106     s = lineend+1;
107   }
108
109   munmap(data, data_len);
110   write_out("\n");
111 }
112
113 // Recursive code dir traversal function
114 void run(char *path) {
115   DIR *dir = opendir(".");
116   if (dir == NULL) perror("can't open ."), exit(1);
117
118   struct dirent dent_buf;
119   struct dirent *cur_ent = NULL;
120   while (1) {
121     readdir_r(dir, &dent_buf, &cur_ent);
122     if (cur_ent == NULL) break;
123     if (cur_ent->d_name[0] == '.') continue; /* ., .. or hidden */
124
125     if (cur_ent->d_type == DT_UNKNOWN) {
126       fprintf(stderr, "your fs returned DT_UNKNOWN in readdir. it's allowed, but dumb."
127                     "\nhandling that case would bloat the code, so... fix it if you need it.\n");
128       exit(1);
129     }
130
131     if (cur_ent->d_type == DT_DIR) {
132       if (strcmp(cur_ent->d_name, "debian") == 0) continue;
133       if (chdir(cur_ent->d_name))
134         fprintf(stderr, "can't chdir into %s: %m\n", cur_ent->d_name), exit(1);
135       char *subpath = NULL;
136       asprintf(&subpath, "%s%s/", path, cur_ent->d_name);
137       if (subpath == NULL)
138         fprintf(stderr, "can't create subpath\n"), exit(1);
139       run(subpath);
140       free(subpath);
141       if (fchdir(dirfd(dir))) perror("can't chdir back"), exit(1);
142     }
143
144     if (cur_ent->d_type == DT_REG) {
145       char *dot = strrchr(cur_ent->d_name, '.');
146       if (dot == NULL) continue;
147       if (dot[1] != 'c' || dot[2] != '\0') continue;
148       // it's a C source file
149       char *subpath = NULL;
150       asprintf(&subpath, "%s%s", path, cur_ent->d_name);
151       if (subpath == NULL)
152         fprintf(stderr, "can't create subpath\n"), exit(1);
153       handle_file(cur_ent->d_name, subpath);
154       free(subpath);
155     }
156   }
157
158   closedir(dir);
159 }
160
161 int main(int argc, char **argv) {
162   if (argc != 3) {
163     fputs("bad invocation: want ./gen_method_list <kernel_src> <outpath>", stderr);
164     exit(1);
165   }
166   char *kernel_src_path = argv[1];
167   char *out_path = argv[2];
168
169   out_fd = open(out_path, O_WRONLY|O_CREAT|O_EXCL, 0755);
170   if (out_fd == -1) fprintf(stderr, "can't create outfile at %s: %m\n", out_path), exit(1);
171
172   if (chdir(kernel_src_path))
173     fprintf(stderr, "can't access kernel sources at %s: %m\n", kernel_src_path), exit(1);
174
175   run("");
176   clear_out();
177 }