[lib][fs] simple file system layer
diff --git a/include/lib/fs.h b/include/lib/fs.h
new file mode 100644
index 0000000..05390cc
--- /dev/null
+++ b/include/lib/fs.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef __LIB_FS_H
+#define __LIB_FS_H
+
+void fs_init(void);
+
+struct file_stat {
+	bool is_dir;
+	off_t size;
+};
+
+typedef void *filecookie;
+typedef void *fscookie;
+
+int fs_mount(const char *path, const char *device);
+int fs_unmount(const char *path);
+
+/* file api */
+int fs_open_file(const char *path, filecookie *fcookie);
+int fs_read_file(filecookie fcookie, void *buf, off_t offset, size_t len);
+int fs_close_file(filecookie fcookie);
+int fs_stat_file(filecookie fcookie, struct file_stat *);
+
+/* convenience routines */
+ssize_t fs_load_file(const char *path, void *ptr, size_t maxlen);
+
+/* walk through a path string, removing duplicate path seperators, flattening . and .. references */
+void fs_normalize_path(char *path);
+
+#endif
+
diff --git a/lib/fs/debug.c b/lib/fs/debug.c
new file mode 100644
index 0000000..6d9b433
--- /dev/null
+++ b/lib/fs/debug.c
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2009 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <debug.h>
+#include <string.h>
+#include <lib/console.h>
+#include <lib/fs.h>
+#include <stdlib.h>
+#include <platform.h>
+
+#if defined(WITH_LIB_CONSOLE)
+
+#if DEBUGLEVEL > 1
+static int cmd_fs(int argc, const cmd_args *argv);
+
+STATIC_COMMAND_START
+STATIC_COMMAND("fs", "fs debug commands", &cmd_fs)
+STATIC_COMMAND_END(fs);
+
+extern int fs_mount_type(const char *path, const char *device, const char *name);
+extern int fs_create_file(const char *path, filecookie *fcookie);
+extern int fs_make_dir(const char *path);
+extern int fs_write_file(filecookie fcookie, const void *buf, off_t offset, size_t len);
+
+static int cmd_fs(int argc, const cmd_args *argv)
+{
+	int rc = 0;
+
+	if (argc < 2) {
+notenoughargs:
+		printf("not enough arguments:\n");
+usage:
+		printf("%s mount <path> <device> [<type>]\n", argv[0].str);
+		printf("%s unmount <path>\n", argv[0].str);
+		printf("%s create <path>\n", argv[0].str);
+		printf("%s mkdir <path>\n", argv[0].str);
+		printf("%s read <path> [<offset>] [<len>]\n", argv[0].str);
+		printf("%s write <path> <string> [<offset>]\n", argv[0].str);
+		printf("%s stat <file>\n", argv[0].str);
+		return -1;
+	}
+
+	if (!strcmp(argv[1].str, "mount")) {
+		int err;
+
+		if (argc < 4)
+			goto notenoughargs;
+
+		if (argc < 5)
+			err = fs_mount(argv[2].str, argv[3].str);
+
+		else
+			err = fs_mount_type(argv[2].str, argv[3].str, argv[4].str);
+
+		if (err < 0) {
+			printf("error %d mounting device\n", err);
+			return err;
+		}
+	} else if (!strcmp(argv[1].str, "unmount")) {
+		int err;
+
+		if (argc < 3)
+			goto notenoughargs;
+
+		err = fs_unmount(argv[2].str);
+		if (err < 0) {
+			printf("error %d unmounting device\n", err);
+			return err;
+		}
+	} else if (!strcmp(argv[1].str, "create")) {
+		int err;
+		filecookie cookie;
+
+		if (argc < 3)
+			goto notenoughargs;
+
+		err = fs_create_file(argv[2].str, &cookie);
+		if (err < 0) {
+			printf("error %d creating file\n", err);
+			return err;
+		}
+
+		fs_close_file(cookie);
+	} else if (!strcmp(argv[1].str, "mkdir")) {
+		int err;
+
+		if (argc < 3)
+			goto notenoughargs;
+
+		err = fs_make_dir(argv[2].str);
+		if (err < 0) {
+			printf("error %d making directory\n", err);
+			return err;
+		}
+	} else if (!strcmp(argv[1].str, "read")) {
+		int err;
+		char *buf;
+		off_t off;
+		size_t len;
+		filecookie cookie;
+		struct file_stat stat;
+
+		if (argc < 3)
+			goto notenoughargs;
+
+		err = fs_open_file(argv[2].str, &cookie);
+		if (err < 0) {
+			printf("error %d opening file\n", err);
+			return err;
+		}
+
+		err = fs_stat_file(cookie, &stat);
+		if (err < 0) {
+			printf("error %d stat'ing file\n", err);
+			fs_close_file(cookie);
+			return err;
+		}
+
+		if (argc < 4)
+			off = 0;
+
+		else
+			off = argv[3].u;
+
+		if (argc < 5)
+			len = stat.size - off;
+
+		else
+			len = argv[4].u;
+
+		buf = malloc(len + 1);
+
+		err = fs_read_file(cookie, buf, off, len);
+		if (err < 0) {
+			printf("error %d reading file\n", err);
+			free(buf);
+			fs_close_file(cookie);
+			return err;
+		}
+
+		buf[len] = '\0';
+		printf("%s\n", buf);
+		free(buf);
+		fs_close_file(cookie);
+	} else if (!strcmp(argv[1].str, "write")) {
+		int err;
+		off_t off;
+		filecookie cookie;
+		struct file_stat stat;
+
+		if (argc < 3)
+			goto notenoughargs;
+
+		err = fs_open_file(argv[2].str, &cookie);
+		if (err < 0) {
+			printf("error %d opening file\n", err);
+			return err;
+		}
+
+		err = fs_stat_file(cookie, &stat);
+		if (err < 0) {
+			printf("error %d stat'ing file\n", err);
+			fs_close_file(cookie);
+			return err;
+		}
+
+		if (argc < 5)
+			off = stat.size;
+
+		else
+			off = argv[4].u;
+
+		err = fs_write_file(cookie, argv[3].str, off, strlen(argv[3].str));
+		if (err < 0) {
+			printf("error %d writing file\n", err);
+			fs_close_file(cookie);
+			return err;
+		}
+
+		fs_close_file(cookie);
+	} else if (!strcmp(argv[1].str, "stat")) {
+		int err;
+		struct file_stat stat;
+		filecookie cookie;
+
+		if (argc < 3)
+			goto notenoughargs;
+
+		err = fs_open_file(argv[2].str, &cookie);
+		if (err < 0) {
+			printf("error %d opening file\n", err);
+			return err;
+		}
+
+		err = fs_stat_file(cookie, &stat);
+		if (err < 0) {
+			printf("error %d statting file\n", err);
+			fs_close_file(cookie);
+			return err;
+		}
+
+		printf("stat successful:\n");		
+		printf("\tis_dir: %d\n", stat.is_dir ? 1 : 0);
+		printf("\tsize: %lld\n", stat.size);
+
+		fs_close_file(cookie);
+	} else {
+		printf("unrecognized subcommand\n");
+		goto usage;
+	}
+
+	return rc;
+}
+
+#endif
+
+#endif
+
diff --git a/lib/fs/fs.c b/lib/fs/fs.c
new file mode 100644
index 0000000..8389a9d
--- /dev/null
+++ b/lib/fs/fs.c
@@ -0,0 +1,518 @@
+/*
+ * Copyright (c) 2009 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <debug.h>
+#include <list.h>
+#include <err.h>
+#include <string.h>
+#include <stdlib.h>
+#include <lib/fs.h>
+#include <lib/bio.h>
+
+#if WITH_LIB_FS_EXT2
+#include <lib/fs/ext2.h>
+#endif
+#if WITH_LIB_FS_FAT32
+#include <lib/fs/fat32.h>
+#endif
+
+#define LOCAL_TRACE 0
+
+struct fs_type {
+	const char *name;
+	int (*mount)(bdev_t *, fscookie *);
+	int (*unmount)(fscookie);
+	int (*open)(fscookie, const char *, filecookie *);
+	int (*create)(fscookie, const char *, filecookie *);
+	int (*mkdir)(fscookie, const char *);
+	int (*stat)(filecookie, struct file_stat *);
+	int (*read)(filecookie, void *, off_t, size_t);
+	int (*write)(filecookie, const void *, off_t, size_t);
+	int (*close)(filecookie);
+};
+
+struct fs_mount {
+	struct list_node node;
+	char *path;
+	bdev_t *dev;
+	fscookie cookie;
+	int refs;
+	struct fs_type *type;
+};
+
+struct fs_file {
+	filecookie cookie;
+	struct fs_mount *mount;
+};
+
+static struct list_node mounts;
+
+static struct fs_type types[] = {
+#if WITH_LIB_FS_EXT2
+	{
+		.name = "ext2",
+		.mount = ext2_mount,
+		.unmount = ext2_unmount,
+		.open = ext2_open_file,
+		.stat = ext2_stat_file,
+		.read = ext2_read_file,
+		.close = ext2_close_file,
+	},
+#endif
+#if WITH_LIB_FS_FAT32
+	{
+		.name = "fat32",
+		.mount = fat32_mount,
+		.unmount = fat32_unmount,
+		.open = fat32_open_file,
+		.create = fat32_create_file,
+		.mkdir = fat32_make_dir,
+		.stat = fat32_stat_file,
+		.read = fat32_read_file,
+		.write = fat32_write_file,
+		.close = fat32_close_file,
+	},
+#endif
+};
+
+static void test_normalize(const char *in);
+static struct fs_mount *find_mount(const char *path, const char **trimmed_path);
+
+void fs_init(void)
+{
+	list_initialize(&mounts);
+#if 0
+	test_normalize("/");
+	test_normalize("/test");
+	test_normalize("/test/");
+	test_normalize("test/");
+	test_normalize("test");
+	test_normalize("/test//");
+	test_normalize("/test/foo");
+	test_normalize("/test/foo/");
+	test_normalize("/test/foo/bar");
+	test_normalize("/test/foo/bar//");
+	test_normalize("/test//foo/bar//");
+	test_normalize("/test//./foo/bar//");
+	test_normalize("/test//./.foo/bar//");
+	test_normalize("/test//./..foo/bar//");
+	test_normalize("/test//./../foo/bar//");
+	test_normalize("/test/../foo");
+	test_normalize("/test/bar/../foo");
+	test_normalize("../foo");
+	test_normalize("../foo/");
+	test_normalize("/../foo");
+	test_normalize("/../foo/");
+	test_normalize("/../../foo");
+	test_normalize("/bleh/../../foo");
+	test_normalize("/bleh/bar/../../foo");
+	test_normalize("/bleh/bar/../../foo/..");
+	test_normalize("/bleh/bar/../../foo/../meh");
+#endif
+}
+
+static struct fs_mount *find_mount(const char *path, const char **trimmed_path)
+{
+	struct fs_mount *mount;
+	size_t pathlen = strlen(path);
+
+	list_for_every_entry(&mounts, mount, struct fs_mount, node) {
+		size_t mountpathlen = strlen(mount->path);
+		if (pathlen < mountpathlen)
+			continue;
+
+		LTRACEF("comparing %s with %s\n", path, mount->path);
+
+		if (memcmp(path, mount->path, mountpathlen) == 0) {
+			if (trimmed_path)
+				*trimmed_path = &path[mountpathlen];
+
+			return mount;
+		}
+	}
+
+	return NULL;
+}
+
+static int mount(const char *path, const char *device, struct fs_type *type)
+{
+	char temppath[512];
+
+	strlcpy(temppath, path, sizeof(temppath));
+	fs_normalize_path(temppath);
+
+	if(temppath[0] != '/')
+		return ERR_BAD_PATH;
+
+	if (find_mount(temppath, NULL))
+		return ERR_ALREADY_MOUNTED;
+
+	bdev_t *dev = bio_open(device);
+	if (!dev)
+		return ERR_NOT_FOUND;
+
+	fscookie cookie;
+	int err = type->mount(dev, &cookie);
+	if (err < 0) {
+		bio_close(dev);
+		return err;
+	}
+
+	/* create the mount structure and add it to the list */
+	struct fs_mount *mount = malloc(sizeof(struct fs_mount));
+	mount->path = strdup(temppath);
+	mount->dev = dev;
+	mount->cookie = cookie;
+	mount->refs = 1;
+	mount->type = type;
+
+	list_add_head(&mounts, &mount->node);
+
+	return 0;
+}
+
+int fs_mount(const char *path, const char *device)
+{
+	return mount(path, device, &types[0]);
+}
+
+int fs_mount_type(const char *path, const char *device, const char *name)
+{
+	size_t i;
+
+	for (i = 0; i < countof(types); i++) {
+		if (!strcmp(name, types[i].name))
+			return mount(path, device, &types[i]);
+	}
+
+	return ERR_NOT_FOUND;
+}
+
+static void put_mount(struct fs_mount *mount)
+{
+	if (!(--mount->refs)) {
+		list_delete(&mount->node);
+		mount->type->unmount(mount->cookie);
+		free(mount->path);
+		bio_close(mount->dev);
+		free(mount);
+	}
+}
+
+int fs_unmount(const char *path)
+{
+	char temppath[512];
+
+	strlcpy(temppath, path, sizeof(temppath));
+	fs_normalize_path(temppath);
+
+	struct fs_mount *mount = find_mount(temppath, NULL);
+	if (!mount)
+		return ERR_NOT_FOUND;
+
+	put_mount(mount);
+
+	return 0;
+}
+
+
+int fs_open_file(const char *path, filecookie *fcookie)
+{
+	int err;
+	char temppath[512];
+	filecookie cookie;
+
+	strlcpy(temppath, path, sizeof(temppath));
+	fs_normalize_path(temppath);
+
+	LTRACEF("path %s temppath %s\n", path, temppath);
+
+	const char *newpath;
+	struct fs_mount *mount = find_mount(temppath, &newpath);
+	if (!mount)
+		return ERR_NOT_FOUND;
+
+	LTRACEF("path %s temppath %s newpath %s\n", path, temppath, newpath);
+
+	err = mount->type->open(mount->cookie, newpath, &cookie);
+	if (err < 0)
+		return err;
+
+	struct fs_file *f = malloc(sizeof(*f));
+	f->cookie = cookie;
+	f->mount = mount;
+	mount->refs++;
+	*fcookie = f;
+
+	return 0;
+}
+
+int fs_create_file(const char *path, filecookie *fcookie)
+{
+	int err;
+	char temppath[512];
+	filecookie cookie;
+
+	strlcpy(temppath, path, sizeof(temppath));
+	fs_normalize_path(temppath);
+
+	const char *newpath;
+	struct fs_mount *mount = find_mount(temppath, &newpath);
+	if (!mount)
+		return ERR_NOT_FOUND;
+
+	if (!mount->type->create)
+		return ERR_NOT_SUPPORTED;
+
+	err = mount->type->create(mount->cookie, newpath, &cookie);
+	if (err < 0)
+		return err;
+
+	struct fs_file *f = malloc(sizeof(*f));
+	f->cookie = cookie;
+	f->mount = mount;
+	mount->refs++;
+	*fcookie = f;
+
+	return 0;
+}
+
+int fs_make_dir(const char *path)
+{
+	char temppath[512];
+
+	strlcpy(temppath, path, sizeof(temppath));
+	fs_normalize_path(temppath);
+
+	const char *newpath;
+	struct fs_mount *mount = find_mount(temppath, &newpath);
+	if (!mount)
+		return ERR_NOT_FOUND;
+
+	if (!mount->type->mkdir)
+		return ERR_NOT_SUPPORTED;
+
+	return mount->type->mkdir(mount->cookie, newpath);
+}
+
+int fs_read_file(filecookie fcookie, void *buf, off_t offset, size_t len)
+{
+	struct fs_file *f = fcookie;
+
+	return f->mount->type->read(f->cookie, buf, offset, len);
+}
+
+int fs_write_file(filecookie fcookie, const void *buf, off_t offset, size_t len)
+{
+	struct fs_file *f = fcookie;
+
+	if (!f->mount->type->write)
+		return ERR_NOT_SUPPORTED;
+
+	return f->mount->type->write(f->cookie, buf, offset, len);
+}
+
+int fs_close_file(filecookie fcookie)
+{
+	int err;
+	struct fs_file *f = fcookie;
+
+	err = f->mount->type->close(f->cookie);
+	if (err < 0)
+		return err;
+
+	put_mount(f->mount);
+	free(f);
+	return 0;
+}
+
+int fs_stat_file(filecookie fcookie, struct file_stat *stat)
+{
+	struct fs_file *f = fcookie;
+
+	return f->mount->type->stat(f->cookie, stat);
+}
+
+ssize_t fs_load_file(const char *path, void *ptr, size_t maxlen)
+{
+	int err;
+	filecookie cookie;
+
+	/* open the file */
+	err = fs_open_file(path, &cookie);
+	if (err < 0)
+		return err;
+
+	/* stat it for size, see how much we need to read */
+	struct file_stat stat;
+	fs_stat_file(cookie, &stat);
+
+	err = fs_read_file(cookie, ptr, 0, MIN(maxlen, stat.size));
+
+	fs_close_file(cookie);
+
+	return err;
+}
+
+static void test_normalize(const char *in)
+{
+	char path[1024];
+
+	strlcpy(path, in, sizeof(path));
+	fs_normalize_path(path);
+	printf("'%s' -> '%s'\n", in, path);
+}
+
+void fs_normalize_path(char *path)
+{
+	int outpos;
+	int pos;
+	char c;
+	bool done;
+	enum {
+		INITIAL,
+		FIELD_START,
+		IN_FIELD,
+		SEP,
+		SEEN_SEP,
+		DOT,
+		SEEN_DOT,
+		DOTDOT,
+		SEEN_DOTDOT,
+	} state;
+
+	state = INITIAL;
+	pos = 0;
+	outpos = 0;
+	done = false;
+
+	/* remove duplicate path seperators, flatten empty fields (only composed of .), backtrack fields with .., remove trailing slashes */
+	while (!done) {
+		c = path[pos];
+		switch (state) {
+			case INITIAL:
+				if (c == '/') {
+					state = SEP;
+				} else if (c == '.') {
+					state = DOT;
+				} else {
+					state = FIELD_START;
+				}
+				break;
+			case FIELD_START:
+				if (c == '.') {
+					state = DOT;
+				} else if (c == 0) {
+					done = true;
+				} else {
+					state = IN_FIELD;
+				}
+				break;
+			case IN_FIELD:
+				if (c == '/') {
+					state = SEP;
+				} else if (c == 0) {
+					done = true;
+				} else {
+					path[outpos++] = c;
+					pos++;
+				}
+				break;
+			case SEP:
+				pos++;
+				path[outpos++] = '/';
+				state = SEEN_SEP;
+				break;
+			case SEEN_SEP:
+				if (c == '/') {
+					// eat it
+					pos++;
+				} else if (c == 0) {
+					done = true;
+				} else {
+					state = FIELD_START;
+				}
+				break;
+			case DOT:
+				pos++; // consume the dot
+				state = SEEN_DOT;
+				break;
+			case SEEN_DOT:
+				if (c == '.') {
+					// dotdot now
+					state = DOTDOT;
+				} else if (c == '/') {
+					// a field composed entirely of a .
+					// consume the / and move directly to the SEEN_SEP state
+					pos++;
+					state = SEEN_SEP;
+				} else if (c == 0) {
+					done = true;
+				} else {
+					// a field prefixed with a .
+					// emit a . and move directly into the IN_FIELD state
+					path[outpos++] = '.';
+					state = IN_FIELD;
+				}
+				break;
+			case DOTDOT:
+				pos++; // consume the dot
+				state = SEEN_DOTDOT;
+				break;
+			case SEEN_DOTDOT:
+				if (c == '/' || c == 0) {
+					// a field composed entirely of '..'
+					// search back and consume a field we've already emitted
+					if (outpos > 0) {
+						// we have already consumed at least one field
+						outpos--;
+
+						// walk backwards until we find the next field boundary
+						while (outpos > 0) {
+							if (path[outpos - 1] == '/') {
+								break;
+							}
+							outpos--;
+						}
+					}
+					pos++;
+					state = SEEN_SEP;
+					if (c == 0)
+						done = true;
+				} else {
+					// a field prefixed with ..
+					// emit the .. and move directly to the IN_FIELD state
+					path[outpos++] = '.';
+					path[outpos++] = '.';
+					state = IN_FIELD;
+				}
+				break;
+		}
+	}
+
+	/* dont end with trailing slashes */
+	if (outpos > 0 && path[outpos - 1] == '/')
+		outpos--;
+
+	path[outpos++] = 0;
+}
+
diff --git a/lib/fs/rules.mk b/lib/fs/rules.mk
new file mode 100644
index 0000000..c1aa7f4
--- /dev/null
+++ b/lib/fs/rules.mk
@@ -0,0 +1,9 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULES += \
+
+#	lib/fs/ext2
+
+OBJS += \
+	$(LOCAL_DIR)/fs.o \
+	$(LOCAL_DIR)/debug.o