fast method_name->source_file lookups, heuristics for finding ioctl names
[moctel.git] / gen_method_list.c
diff --git a/gen_method_list.c b/gen_method_list.c
new file mode 100644 (file)
index 0000000..94572f8
--- /dev/null
@@ -0,0 +1,177 @@
+#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();
+}