Abstract EXT2, add JMK Emacs integration
diff --git a/.dir-locals.el b/.dir-locals.el
new file mode 100644
index 0000000..2648a7e
--- /dev/null
+++ b/.dir-locals.el
@@ -0,0 +1,3 @@
+((nil . ((eval
+		  . (setq-local flycheck-clang-args
+						(jmk-arguments-for (expand-file-name buffer-file-name)))))))
diff --git a/.gitignore b/.gitignore
index ee4a8e6..9da708c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,8 @@
 mnt/*
 doc/_dox*
 .ccls*
+GTAGS
+GPATh
+GRTAGS
+GPATH
+.projectile
diff --git a/bin/jmk b/bin/jmk
index ffd3993..ef6d057 100755
--- a/bin/jmk
+++ b/bin/jmk
@@ -6,8 +6,21 @@
 root="$(dirname $dir)"
 jmkscript="$root/share/jmk/jmk.m4"
 
+disable_gtags=false
+
+while getopts "Gh" arg; do
+    case $arg in
+	G) gen_gtags=false ;;
+	h) echo "Please read the JMK manual for more information." ;;
+	?) echo "Invalid argument"
+	   exit 1 ;;
+    esac
+done
+
 for file in $(find -name Jmk); do
     echo "Processing $file" > /dev/stderr
     outdir="$(dirname $file)"
-    m4 -D jmk_root="$root" -D jmk_build_cmd="$0" -D jmk_build_dir="$(pwd)" "$jmkscript" "$file" > "$outdir/Makefile" || exit 1
+    m4 -D jmk_root="$root" -D jmk_build_cmd="$0" -D jmk_build_dir="$(pwd)" \
+       -D disable_gtags="$disable_gtags" "$jmkscript" "$file" > "$outdir/Makefile" \
+       || exit 2
 done
diff --git a/include/kernel/dri/fs/ext2/ext2.h b/include/kernel/dri/fs/ext2/ext2.h
index df2b0fe..5b587c9 100644
--- a/include/kernel/dri/fs/ext2/ext2.h
+++ b/include/kernel/dri/fs/ext2/ext2.h
@@ -237,6 +237,12 @@
 /// File type flag (used on inode->mode)
 #define EXT2_F_TYPE 0xf000
 
+const extern uchar ext2_s_to_ft[];
+
+/// Converts the file "mode" (inode->mode, EXT2_S_*) to a file type
+/// (EXT2_FT_*).
+#define EXT2_S_TO_FT(s) (ext2_s_to_ft[s >> 12])
+
 struct ext2_dirent
 {
 	uint inode;
@@ -258,34 +264,53 @@
 	EXT2_FT_SYMLINK,
 };
 
-/// Read a file system block (0-indexed), if necessary multiple disk blocks
-/// will be read automatically
-void ext2_read_block(struct ext2_superblock *sb, void *buffer, uint block);
-void ext2_write_block(struct ext2_superblock *sb, void *buffer, uint block);
+/// Read a file system block (0-indexed), if necessary multiple disk
+/// blocks will be read automatically
+void ext2_read_block(struct ext2_superblock *sb, void *buffer,
+					 uint block);
+void ext2_write_block(struct ext2_superblock *sb, void *buffer,
+					  uint block);
 
 struct ext2_superblock ext2_read_superblock();
 
 void ext2_write_superblock(struct ext2_superblock *sb);
 
-/// Just for fun, corrupt the superblock (obviously don't actually do this)
+/// Just for fun, corrupt the superblock (obviously don't actually do
+/// this)
 void ext2_corrupt_superblock_for_fun();
 
 void ext2_mount(struct fs_node *where);
 
 bool ext2_valid_filesystem();
 
-bool ext2_find_inode(struct ext2_superblock *sb, uint number, struct ext2_inode *inode);
+/**
+ * @brief Get or set an inode
+ * @param inode Either a pointer to the value to be read (set=false),
+ * or the value to be written (set=true).
+ * @param set True if the inode should be set (written), false
+ * otherwise.
+ */
+bool ext2_get_or_set_inode(struct ext2_superblock *sb, uint number,
+						   struct ext2_inode *inode, bool set);
+
+bool ext2_find_inode(struct ext2_superblock *sb, uint number,
+					 struct ext2_inode *inode);
+
+bool ext2_set_inode(struct ext2_superblock *sb, uint number,
+					struct ext2_inode *inode);
 
 /// Load a block group descriptor for a certain block group
 struct ext2_block_group_descriptor ext2_load_block_group_descriptor(
 	struct ext2_superblock *sb, uint block_group);
 
-/// List the contents of a directory dir. Calls `cb` for each item. If `dir` is
-/// not a directory, returns false. Otherwise returns true.
+/// List the contents of a directory dir. Calls `cb` for each item. If
+/// `dir` is not a directory, returns false. Otherwise returns true.
+/// if cb returns true, ls will continue. Otherwise it will stop.
 bool ext2_dir_ls(struct ext2_superblock *sb,
 				 struct ext2_inode *dir,
-				 void (*cb)(uint inode,
+				 bool (*cb)(uint inode,
 							const char *name,
+							uint name_len,
 							void *data),
 				 void *data);
 
@@ -341,3 +366,36 @@
 
 void ext2_insert_into_dir(struct ext2_superblock *sb, struct ext2_inode *dir,
 						  char *name, uint name_len, uint inode, uchar type);
+
+/**
+ * Creates a hard link
+ * @param dir Directory to create the hard link in
+ * @param name Name of the link  to create
+ * @param name_len length of name, not including (optional) NULL byte
+ * @param inode Inode number of link target
+ */
+bool ext2_hard_link(struct ext2_superblock *sb, struct ext2_inode *dir,
+                    char *name, uint name_len, uint inode);
+
+/**
+ * @brief Does a directory contain a certain entry?
+ * @param sb Superblock
+ * @param dir The directory to search in
+ * @param name The name of the entry to look for
+ * @param len The length of the name, not including the optional NULL byte
+ * @returns True if the entry is found, false if not, or if something
+ * goes wrong (ie: dir is not a directory)
+ */
+bool ext2_dir_contains(struct ext2_superblock *sb, struct ext2_inode *dir,
+					   char *name, uint len);
+
+/**
+ * @brief Find an entry in a directory
+ * @param dir Directory to search in
+ * @param name Name of file
+ * @param name_len Length of name, not including (optional) NULL byte.
+ * @returns 0 if no entry could be found, otherwise inode number for
+ * that entry
+ */
+uint ext2_dir_find(struct ext2_superblock *sb, struct ext2_inode *dir,
+				   char *name, uint name_len);
diff --git a/include/kernel/io.h b/include/kernel/io.h
index be82998..edfe2c7 100644
--- a/include/kernel/io.h
+++ b/include/kernel/io.h
@@ -73,6 +73,8 @@
  * @returns the length of null-terminated string a.
  */
 uint strlen(char *a);
+uint strnlen(char *s, size_t len);
+int strncmp(char *a, char *b, size_t len);
 
 bool isdigit(char c);
 /**
diff --git a/include/kernel/kint.h b/include/kernel/kint.h
index e3e12d7..79af0ab 100644
--- a/include/kernel/kint.h
+++ b/include/kernel/kint.h
@@ -21,6 +21,8 @@
 #define MIN(a, b) ((a)>(b)?(b):(a))
 #define MAX(a, b) ((a)>(b)?(a):(b))
 
+#define UNUSED(val) ((void)(val));
+
 /// Pads num to an integer size boundary
 #define PAD(num) ((num + 3) & (~0b11))
 
diff --git a/share/jmk/dir-locals.el b/share/jmk/dir-locals.el
new file mode 100644
index 0000000..2648a7e
--- /dev/null
+++ b/share/jmk/dir-locals.el
@@ -0,0 +1,3 @@
+((nil . ((eval
+		  . (setq-local flycheck-clang-args
+						(jmk-arguments-for (expand-file-name buffer-file-name)))))))
diff --git a/share/jmk/jmk-flycheck.el b/share/jmk/jmk-flycheck.el
new file mode 100644
index 0000000..8de4949
--- /dev/null
+++ b/share/jmk/jmk-flycheck.el
@@ -0,0 +1,37 @@
+(defun jmk-find-parent-makefile (p)
+  "Find the parent makefile of file P."
+  (let ((path (or p (expand-file-name "."))))
+	(while (not (or (string= path "/")
+					(file-exists-p (concat path "/Makefile"))))
+	  (setq path (expand-file-name (concat path "/.."))))
+	(unless (string= path "/")
+	  (concat path "/Makefile"))))
+
+(defun jmk-find-makefile (p)
+  "Find the makefile for the source file P.
+/a/b/src/hello.c -> /a/b/src/Makefile
+/a/b/include/hello.h -> /a/b/src/Makefile
+Returns nil if nothing can be found"
+  (if-let ((makefile (jmk-find-parent-makefile p)))
+	  makefile
+	(jmk-find-parent-makefile (replace-regexp-in-string
+							   (regexp-quote "include")
+							   "src" p nil 'literal))))
+
+(defun jmk-arguments-for (p)
+  (if-let (path (jmk-find-makefile p))
+	  (split-string (shell-command-to-string
+					 (concat "make -f " path " show_cflags")) " ")
+	nil))
+
+(defun jmk-includes-for (p)
+  (let* ((args (jmk-arguments-for p))
+		 (includes (cl-remove-if-not (lambda (arg)
+									   (string-prefix-p "-I" arg))
+									 args))
+		 (names (mapcar (lambda (arg)
+						  (string-trim (substring arg 2)))
+						includes)))
+	(message "includes: %s" names)
+	names))
+
diff --git a/share/jmk/jmk.m4 b/share/jmk/jmk.m4
index e251a8b..ce95156 100644
--- a/share/jmk/jmk.m4
+++ b/share/jmk/jmk.m4
@@ -9,9 +9,12 @@
 define(`_foreach', `ifelse(`$2', `()', `',
   `define(`$1', _arg1$2)$3`'$0(`$1', (shift$2), `$3')')')
 
-define(status_log, `	@printf " \e[1;34m%8s\e[m  %s\n" "$1" "$2"')
+define(status_log, `	@printf " \e[1;34m%8s\e[m  %s\n" "$1" "$2" > /dev/stderr')
 define(DO_MAKE, `$(MAKE) --no-print-directory MAKEFILE_DEPTH="$$(( $(MAKEFILE_DEPTH) + 1))"')
 
+define(gtags_path, $(ROOT)/GTAGS)
+define(gen_gtags, ifelse(disable_gtags,true,,cd $(ROOT) && gtags))
+
 define(init,
 `jmk_project := $1
 jmk_target = ifelse(`$#', 2, $2, $1)
@@ -49,10 +52,10 @@
 
 dnl archetype enables a language archetype
 define(archetype,
-    `ifelse($1, c, `.c.o:
+    `ifelse($1, c, `.c.o: gtags_path
 status_log(CC, $<)
 	@$(CC) -c $< -o dollar_at $(CFLAGS)',
-        $1, asm, `.s.o:
+        $1, asm, `.s.o: gtags_path
 status_log(AS, $<)
 	@$(ASM) $(ASMFLAGS) $< -o dollar_at')')
 
@@ -81,14 +84,17 @@
     `ifelse($1, executable,
 `$(jmk_target): $(OBJECTS)
 status_log(LD, dollar_at)
+	@gen_gtags
 	@$(CC) -o dollar_at $^ $(CFLAGS)',
     $1, static_lib,
 `$(jmk_target): $(OBJECTS)
 status_log(AR, dollar_at)
+	@gen_gtags
 	@ar rcs dollar_at $^',
     $1, custom_link,
 `$(jmk_target): $(OBJECTS)
 status_log(LD, dollar_at)
+	@gen_gtags
 	@$(LD) $(LDFLAGS) -o dollar_at $^')')
 
 define(option,
@@ -107,6 +113,13 @@
 status_log(JMK, jmk_build_dir)
 	@cd "jmk_build_dir" && jmk_build_cmd
 
-.PHONY: $(jmk_libs_phony) $(jmk_custom_phony) $(jmk_clean_libs) clean all')
+gtags_path:
+status_log(GTAGS,)
+	gen_gtags
+
+show_cflags:
+	@echo $(CFLAGS)
+
+.PHONY: $(jmk_libs_phony) $(jmk_custom_phony) $(jmk_clean_libs) clean all show_cflags')
 
 divert(0)dnl
diff --git a/src/kernel/Jmk b/src/kernel/Jmk
index 4b78fbb..09ebdfd 100644
--- a/src/kernel/Jmk
+++ b/src/kernel/Jmk
@@ -1,3 +1,5 @@
+# -*- mode:makefile -*-
+
 init(kernel, kernel.elf)
 
 preset(freestanding)
@@ -25,7 +27,7 @@
 
 FS ?= ext2
 
-CFLAGS += -I $(ROOT)/include/kernel $(test_defines) -O0
+CFLAGS += -I$(ROOT)/include/kernel $(test_defines) -O0
 
 LDFLAGS += -Tlink.ld -melf_i386
 ASMFLAGS += -felf -Fdwarf
diff --git a/src/kernel/dri/fs/ext2/ext2.c b/src/kernel/dri/fs/ext2/ext2.c
index 1ac52b9..9ecbe51 100644
--- a/src/kernel/dri/fs/ext2/ext2.c
+++ b/src/kernel/dri/fs/ext2/ext2.c
@@ -5,6 +5,20 @@
 #include <kint.h>
 #include <log.h>
 
+#define F(f,t) [EXT2_S_##f >> 12] = EXT2_FT_##t,
+const uchar ext2_s_to_ft[] =
+{
+	0,
+	F(IFSOCK, SOCK)
+	F(IFLINK, SYMLINK)
+	F(IFREG, REGULAR_FILE)
+	F(IFBLK, BLKDEV)
+	F(IFDIR, DIR)
+	F(IFCHR, CHRDEV)
+	F(IFIFO, FIFO)
+};
+#undef F
+
 inline uint ext2_block_size(struct ext2_superblock *sb)
 {
 	return 1024 << sb->block_size_shift;
@@ -86,11 +100,11 @@
 	return descriptors[bgd_offset];
 }
 
-static void print_entry(uint inode, const char *name, void *sb)
+static bool print_entry(uint inode, const char *name, uint l, void *sb)
 {
 	kprintf("%d\t %s\n", inode, name);
 
-	return;
+	return true;
 
 	struct ext2_inode in;
 
@@ -106,11 +120,13 @@
 		}
 	}
 
-	return;
+	return true;
 }
 
 void ext2_mount(struct fs_node *where)
 {
+	UNUSED(where)
+
 	struct ext2_superblock sb = ext2_read_superblock();
 
 	kprintf(DEBUG "EXT2 magic = 0x%x\n", sb.signature);
@@ -128,8 +144,7 @@
 
 		char *name = "hello-hl.txt";
 		kprintf(INFO "Creating hard link %s -> hello.txt\n", name);
-		ext2_insert_into_dir(&sb, &root, name, strlen(name), 12,
-							 EXT2_FT_REGULAR_FILE);
+		ext2_hard_link(&sb, &root, name, strlen(name), 12);
 
 		kprintf("ls /\n");
 		kprintf("inode\t name\n");
@@ -168,8 +183,8 @@
 	ext2_write_superblock(&sb);
 }
 
-bool ext2_find_inode(struct ext2_superblock *sb, uint number,
-					 struct ext2_inode *inode)
+bool ext2_get_or_set_inode(struct ext2_superblock *sb, uint number,
+						   struct ext2_inode *inode, bool set)
 {
 	if (number == 0)
 		return false;
@@ -181,38 +196,60 @@
 	struct ext2_block_group_descriptor descriptor =
 		ext2_load_block_group_descriptor(sb, block_group);
 
-	// kprintf(DEBUG "Descriptor inode_table = 0x%x\n",
-	// descriptor.inode_table_start_block);
-
 	// We need to figure out what FS block the inode is on, we know how many
 	// inodes there are total in this BGD and the number per page, so this is
 	// simple.
 
 	const uint block_size = ext2_block_size(sb);
 
-	const uint inodes_per_block = block_size / sizeof(struct ext2_inode);
+	const uint inodes_per_block =
+		block_size / sizeof(struct ext2_inode);
 
 	uint inode_block = local_index / inodes_per_block;
 	uint inode_index = local_index % inodes_per_block;
 
 	struct ext2_inode inodes[block_size / sizeof(struct ext2_inode)];
 
-	ext2_read_block(sb, inodes,
-					descriptor.inode_table_start_block + inode_block);
+	const uint fs_block = descriptor.inode_table_start_block +
+		inode_block;
 
-	*inode = inodes[inode_index];
+	ext2_read_block(sb, inodes, fs_block);
+
+	if (set)
+	{
+		inodes[inode_index] = *inode;
+
+		ext2_write_block(sb, inodes, fs_block);
+	}
+	else
+	{
+		*inode = inodes[inode_index];
+	}
 
 	return true;
 }
 
+bool ext2_find_inode(struct ext2_superblock *sb, uint number,
+					 struct ext2_inode *inode)
+{
+	return ext2_get_or_set_inode(sb, number, inode, false);
+}
+
+bool ext2_set_inode(struct ext2_superblock *sb, uint number,
+					struct ext2_inode *inode)
+{
+	return ext2_get_or_set_inode(sb, number, inode, true);
+}
+
 bool ext2_dir_ls(struct ext2_superblock *sb, struct ext2_inode *dir,
-				 void (*cb)(uint inode, const char *name, void *data),
+				 bool (*cb)(uint inode, const char *name, uint name_len,
+							void *data),
 				 void *data)
 {
 	if ((dir->mode & 0xf000) != EXT2_S_IFDIR)
 		return false;
 
-	for (int i = 0; i < dir->num_blocks; i++)
+	for (uint i = 0; i < dir->num_blocks; i++)
 	{
 		uchar buffer[ext2_block_size(sb)];
 		ext2_read_inode_block(sb, dir, buffer, i);
@@ -233,8 +270,8 @@
 				memcpy(name, ent->name, name_len);
 				name[name_len] = '\0';
 
-				// kprintf("@ b=%d,ent=%p\n", i, (uint)ent - (uint)buffer);
-				cb(ent->inode, name, data);
+				if (!cb(ent->inode, name, name_len, data))
+					return true;
 			}
 
 			ent = (void *)(((uint)(void *)ent) + ent->rec_len);
@@ -256,6 +293,35 @@
 			ent->name, ent->rec_len);
 }
 
+bool ext2_hard_link(struct ext2_superblock *sb, struct ext2_inode *dir,
+                    char *name, uint name_len, uint inode)
+{
+	struct ext2_inode in;
+
+	if (!ext2_dir_contains(sb, dir, name, name_len) &&
+		ext2_find_inode(sb, inode, &in))
+	{
+		if ((dir->mode & 0xf000) != EXT2_S_IFDIR)
+		{
+			return false;
+		}
+
+		// Increment the reference count to this inode
+		in.links_count++;
+		ext2_set_inode(sb, inode, &in);
+
+		// Insert it into the directory
+		uchar type = EXT2_S_TO_FT(in.mode);
+		ext2_insert_into_dir(sb, dir, name, name_len, inode, type);
+
+		return true;
+	}
+	else
+	{
+		return false;
+	}	
+}
+
 void ext2_insert_into_dir(struct ext2_superblock *sb, struct ext2_inode *dir,
 						  char *name, uint name_len, uint inode, uchar type)
 {
@@ -303,7 +369,8 @@
 				PAD(ent->name_len + sizeof(struct ext2_dirent));
 			uint available_size = ent->rec_len - this_min_size;
 
-			// kprintf(",%d=%d/%d", ent->name_len, this_min_size, available_size);
+			// kprintf(",%d=%d/%d", ent->name_len, this_min_size,
+			// available_size);
 
 			if (available_size >= min_size)
 			{
@@ -560,3 +627,82 @@
 
 	return 0;
 }
+
+struct ext2_dir_contains_data
+{
+	char *name;
+	uint len;
+	bool found;
+};
+
+static bool ext2_dir_contains_cb(uint inode, const char *name,
+								uint name_len,
+								struct ext2_dir_contains_data *d)
+{
+	if (strncmp((char *)name, d->name, MIN(name_len, d->len)) == 0)
+	{
+		d->found = true;
+		return false;
+	}
+
+	return true;
+}
+
+bool ext2_dir_contains(struct ext2_superblock *sb, struct ext2_inode *dir,
+					   char *name, uint len)
+{
+	if ((dir->mode & EXT2_F_TYPE) != EXT2_S_IFDIR)
+	{
+		return false;
+	}
+
+	struct ext2_dir_contains_data d =
+		{
+			.name = name,
+			.len = len,
+			.found = false,
+		};
+
+	ext2_dir_ls(sb, dir, ext2_dir_contains_cb, &d);
+
+	return d.found;
+}
+
+struct ext2_dir_find_data
+{
+	char *name;
+	uint len;
+	uint inode;
+};
+
+bool ext2_dir_find_cb(uint inode, const char *name, uint len,
+					  struct ext2_dir_find_data *d)
+{
+	if (strncmp((char *)name, d->name, MIN(len, d->len)) == 0)
+	{
+		d->inode = inode;
+		return false;
+	}
+
+	return true;
+}
+
+uint ext2_dir_find(struct ext2_superblock *sb, struct ext2_inode *dir,
+				   char *name, uint name_len)
+{
+	if ((dir->mode & EXT2_F_TYPE) != EXT2_S_IFDIR)
+	{
+		return false;
+	}
+
+	struct ext2_dir_find_data d =
+		{
+			.name = name,
+			.len = name_len,
+			.inode = 0,
+		};
+
+	ext2_dir_ls(sb, dir, ext2_dir_find_cb, &d);
+
+	return d.inode;
+}
diff --git a/src/kernel/io.c b/src/kernel/io.c
index 0ba13ae..1c10b53 100644
--- a/src/kernel/io.c
+++ b/src/kernel/io.c
@@ -30,6 +30,13 @@
 	return ret;
 }
 
+struct ext2_dir_contains_data
+{
+	char *name;
+	uint len;
+	bool found;
+};
+
 void outl(ushort port, uint val)
 {
 	asm("outl %1, %0" : : "dN"(port), "a"(val));
@@ -85,6 +92,32 @@
 	return i;
 }
 
+uint strnlen(char *s, size_t len)
+{
+	int i;
+
+	for (i = 0; s[i] && i < len; i++)
+	{
+	}
+
+	return i;
+}
+
+int strncmp(char *a, char *b, size_t len)
+{
+	int al = strnlen(a, len), bl = strnlen(b, len);
+
+	if (al != bl)
+		return bl - al;
+
+	for (int i = 0; i < al; i++)
+	{
+		if (a[i] != b[i])
+			return -1;
+	}
+	return 0;
+}
+
 int strcmp(char *a, char *b)
 {
 	int al = strlen(a), bl = strlen(b);