+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <dirent.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int out_fd;
+char outbuf[10000];
+int outbuf_i = 0;
+
+void clear_out(void) {
+ if (write(out_fd, outbuf, outbuf_i) != outbuf_i) {
+ fprintf(stderr, "write to outfile did not succeed\n");
+ exit(1);
+ }
+ outbuf_i = 0;
+}
+
+void write_out(char *buf) {
+ size_t len = strlen(buf);
+ assert(len <= 500);
+ memcpy(outbuf+outbuf_i, buf, len);
+ outbuf_i += len;
+ if (outbuf_i > 9500) clear_out();
+}
+
+/* Maps a textfile into RAM. Returns a pointer to a read-only memory
+ area containing the 0-terminated data. len includes the nullbyte.
+ Free it using munmap. */
+char *map_textfile(char *path, size_t *len) {
+ int fd = open(path, O_RDONLY);
+ if (fd == -1) fprintf(stderr, "can't open %s: %m\n", path), exit(1);
+
+ struct stat st;
+ if (fstat(fd, &st)) fprintf(stderr, "can't stat %s: %m\n", path), exit(1);
+ *len = st.st_size;
+
+ char *data = mmap(NULL, *len+1, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
+ if (data == MAP_FAILED) fprintf(stderr, "can't mmap %s: %m\n", path), exit(1);
+
+ close(fd);
+
+ if ((*len & 4095) == 0) {
+ // TODO: is this ok? does the previous mmap make sure that that address exists?
+ void *nulls_addr = mmap(data+*len, 1, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
+ if (nulls_addr != data+*len)
+ fprintf(stderr, "the mmap trick for 0-termination failed :(\n"), exit(1);
+ *len += 1;
+ }
+
+ return data;
+}
+
+bool isichar(char c) {
+ return (c>='a'&&c<='z') || (c>='A'&&c<='Z') || (c>='0'&&c<='9') || c=='_';
+}
+
+void handle_file(char *name, char *path) {
+ size_t data_len;
+ char *data = map_textfile(name, &data_len);
+
+ bool wrote_path = false;
+
+ /* look for stuff that looks like a method definition. this means:
+ * - line starts with a non-whitespace
+ * - there is a token (the name) followed by an opening paren
+ * - the next { is earlier than the next ;
+ */
+ char *s = data;
+ while (1) {
+ char *lineend = strchr(s, '\n');
+ if (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\0') goto next;
+ if (lineend) *lineend = '\0';
+ char *paren = strchr(s, '(');
+ if (paren == NULL) goto next;
+ *lineend = '\n';
+ char *next_curly = strchr(paren, '{');
+ if (next_curly == NULL) goto next;
+ char *next_semicolon = strchr(paren, ';');
+ if (next_semicolon != NULL && next_semicolon < next_curly)
+ goto next; /* declaration, not definition */
+ char *nameend = paren;
+ while (nameend>s+1 && !isichar(nameend[-1])) nameend--; /* let nameend point behind the name */
+ *nameend = '\0';
+ char *name = nameend-1;
+ while (name>s && isichar(name[-1])) name--;
+ if (!wrote_path) {
+ write_out(path);
+ write_out(":");
+ wrote_path = true;
+ } else {
+ write_out(",");
+ }
+ write_out(name);
+
+next:
+ if (!lineend) break;
+ s = lineend+1;
+ }
+
+ munmap(data, data_len);
+ write_out("\n");
+}
+
+// Recursive code dir traversal function
+void run(char *path) {
+ DIR *dir = opendir(".");
+ if (dir == NULL) perror("can't open ."), exit(1);
+
+ struct dirent dent_buf;
+ struct dirent *cur_ent = NULL;
+ while (1) {
+ readdir_r(dir, &dent_buf, &cur_ent);
+ if (cur_ent == NULL) break;
+ if (cur_ent->d_name[0] == '.') continue; /* ., .. or hidden */
+
+ if (cur_ent->d_type == DT_UNKNOWN) {
+ fprintf(stderr, "your fs returned DT_UNKNOWN in readdir. it's allowed, but dumb."
+ "\nhandling that case would bloat the code, so... fix it if you need it.\n");
+ exit(1);
+ }
+
+ if (cur_ent->d_type == DT_DIR) {
+ if (strcmp(cur_ent->d_name, "debian") == 0) continue;
+ if (chdir(cur_ent->d_name))
+ fprintf(stderr, "can't chdir into %s: %m\n", cur_ent->d_name), exit(1);
+ char *subpath = NULL;
+ asprintf(&subpath, "%s%s/", path, cur_ent->d_name);
+ if (subpath == NULL)
+ fprintf(stderr, "can't create subpath\n"), exit(1);
+ run(subpath);
+ free(subpath);
+ if (fchdir(dirfd(dir))) perror("can't chdir back"), exit(1);
+ }
+
+ if (cur_ent->d_type == DT_REG) {
+ char *dot = strrchr(cur_ent->d_name, '.');
+ if (dot == NULL) continue;
+ if (dot[1] != 'c' || dot[2] != '\0') continue;
+ // it's a C source file
+ char *subpath = NULL;
+ asprintf(&subpath, "%s%s", path, cur_ent->d_name);
+ if (subpath == NULL)
+ fprintf(stderr, "can't create subpath\n"), exit(1);
+ handle_file(cur_ent->d_name, subpath);
+ free(subpath);
+ }
+ }
+
+ closedir(dir);
+}
+
+int main(int argc, char **argv) {
+ if (argc != 3) {
+ fputs("bad invocation: want ./gen_method_list <kernel_src> <outpath>", stderr);
+ exit(1);
+ }
+ char *kernel_src_path = argv[1];
+ char *out_path = argv[2];
+
+ out_fd = open(out_path, O_WRONLY|O_CREAT|O_EXCL, 0755);
+ if (out_fd == -1) fprintf(stderr, "can't create outfile at %s: %m\n", out_path), exit(1);
+
+ if (chdir(kernel_src_path))
+ fprintf(stderr, "can't access kernel sources at %s: %m\n", kernel_src_path), exit(1);
+
+ run("");
+ clear_out();
+}