fatblock: Program to offer dir as FAT32 filesystem using ublock

Change-Id: I6712e062e17b02c453ce89a52000cd8bc3ee810d
diff --git a/fatblock/Android.mk b/fatblock/Android.mk
new file mode 100644
index 0000000..07d9cc1
--- /dev/null
+++ b/fatblock/Android.mk
@@ -0,0 +1,25 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := fatblock
+LOCAL_SRC_FILES := fat.c fatblock.c fs.c import.c read.c utils.c fdpool.c
+# TODO: Why doesn't this work?
+#LOCAL_C_INCLUDES := $(call include-path-for, libublock)/include
+LOCAL_C_INCLUDES := system/extras/libublock/include
+LOCAL_SHARED_LIBRARIES := libublock
+
+include $(BUILD_EXECUTABLE)
diff --git a/fatblock/MODULE_LICENSE_APACHE2 b/fatblock/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/fatblock/MODULE_LICENSE_APACHE2
diff --git a/fatblock/errors.h b/fatblock/errors.h
new file mode 100644
index 0000000..addfb18
--- /dev/null
+++ b/fatblock/errors.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ERRORS_H
+#define ERRORS_H
+
+#define MALLOC_FAIL (-41)    /* memory allocation failed somewhere. */
+#define SKY_IS_FALLING (-42) /* One of the files changed out from under us. */
+
+#endif
diff --git a/fatblock/extent.h b/fatblock/extent.h
new file mode 100644
index 0000000..bbf32a4
--- /dev/null
+++ b/fatblock/extent.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef EXTENT_H
+#define EXTENT_H
+
+#include "types.h"
+
+typedef enum {
+	EXTENT_TYPE_BOOT,
+	EXTENT_TYPE_INFO,
+	EXTENT_TYPE_FAT,
+	EXTENT_TYPE_FILE,
+	EXTENT_TYPE_DIR
+} extent_type;
+
+struct extent {
+	offset_t start;
+	offset_t len;
+	extent_type type;
+
+	struct extent *next;
+};
+
+#endif
diff --git a/fatblock/fat.c b/fatblock/fat.c
new file mode 100644
index 0000000..340f667
--- /dev/null
+++ b/fatblock/fat.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <string.h>
+
+#include <sys/endian.h>
+
+#include "fat.h"
+
+const char FAT_BOOT_SIG[] = { 0x55, 0xAA };
+const char FAT_INFO_SIG1[4] = { 'R', 'R', 'a', 'A' };
+const char FAT_INFO_SIG2[4] = { 'r', 'r', 'A', 'a' };
+
+void fat_dirent_set_first_cluster(struct fat_dirent *de, cluster_t cluster) {
+	assert(de);
+
+	de->first_cluster_hi = htole16((cluster >> 16) & 0xffff);
+	de->first_cluster_lo = htole16((cluster >>  0) & 0xffff);
+}
+
+void fat_dirent_set(struct fat_dirent *de,
+                    char *name, uint8_t attr,
+                    cluster_t first_cluster, uint32_t size) {
+	assert(de);
+	assert(name);
+
+	memset(de, 0, sizeof(*de));
+
+	memcpy(de->name, name, 11);
+	de->attr = attr;
+	fat_dirent_set_first_cluster(de, first_cluster);
+	de->size = htole32(size);
+}
diff --git a/fatblock/fat.h b/fatblock/fat.h
new file mode 100644
index 0000000..4c66b3b
--- /dev/null
+++ b/fatblock/fat.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FAT_H
+#define FAT_H
+
+#include <stdint.h>
+
+#include "types.h"
+
+typedef uint64_t sector_t;
+typedef cluster_t fat_entry_t;
+
+struct fat_boot_sector {
+	uint8_t jump[3];
+	char name[8];
+	uint16_t sector_size;
+	uint8_t sectors_per_cluster;
+	uint16_t reserved_sectors;
+	uint8_t fats;
+	uint16_t rootdir_size;
+	uint16_t sectors16;
+	uint8_t media_desc;
+	uint16_t fat_sectors16;
+	uint16_t sectors_per_track;
+	uint16_t heads;
+	uint32_t hidden_sectors;
+	uint32_t sectors32;
+	uint32_t fat_sectors32;
+	uint16_t fat_flags;
+	uint16_t version;
+	cluster_t rootdir_start;
+	uint16_t fs_info_sector;
+	uint16_t backup_boot_sector;
+	uint8_t reserved1[12];
+	uint8_t phys_drive;
+	uint8_t reserved2;
+	uint8_t ext_boot_sig;
+	uint32_t serial;
+	char vol_label[11];
+	char type[8];
+	char boot_code[420];
+	uint8_t boot_sig[2];
+} __attribute__((__packed__));
+
+#define FAT_MEDIA_DESC_FIXED 0xF8
+
+#define FAT_PHYS_DRIVE_REMOVABLE 0x00
+#define FAT_PHYS_DRIVE_FIXED     0x80
+
+#define FAT_EXT_BOOT_SIG 0x29
+
+extern const char FAT_BOOT_SIG[2];
+
+extern const char FAT_INFO_SIG1[4];
+extern const char FAT_INFO_SIG2[4];
+#define FAT_INFO_SIG3 FAT_BOOT_SIG
+
+struct fat_info_sector {
+	char info_sig1[4];
+	char reserved1[480];
+	char info_sig2[4];
+	cluster_t free_clusters;
+	cluster_t last_cluster;
+	char reserved2[14];
+	char info_sig3[2];
+} __attribute__((__packed__));
+
+struct fat_bootinfo {
+	struct fat_boot_sector boot;
+	struct fat_info_sector info;
+} __attribute__((__packed__));
+
+struct fat_dirent {
+	char name[11];
+	uint8_t attr;
+	uint8_t reserved;
+	uint8_t ctime_ms;
+	uint16_t ctime;
+	uint16_t cdate;
+	uint16_t adate;
+	uint16_t first_cluster_hi;
+	uint16_t mtime;
+	uint16_t mdate;
+	uint16_t first_cluster_lo;
+	uint32_t size;
+} __attribute__((__packed__));
+
+#define FAT_ATTR_READONLY 0x01
+#define FAT_ATTR_HIDDEN   0x02
+#define FAT_ATTR_SYSTEM   0x04
+#define FAT_ATTR_VOLLABEL 0x08
+#define FAT_ATTR_SUBDIR   0x10
+#define FAT_ATTR_ARCHIVE  0x20
+#define FAT_ATTR_DEVICE   0x40
+
+#define FAT_ENTRY_FREE  0x00000000
+#define FAT_ENTRY_BAD   0x0FFFFFF7
+#define FAT_ENTRY_EOC   0x0FFFFFF8
+
+#define FAT_SECTOR_SIZE 512
+#define FAT_CLUSTER_ZERO 2
+#define FAT_ENTRIES_PER_SECTOR ((SECTOR_SIZE) / (sizeof(fat_entry_t)))
+
+void fat_dirent_set_first_cluster(struct fat_dirent *de, cluster_t cluster);
+void fat_dirent_set(struct fat_dirent *de,
+                    char *name, uint8_t attr,
+                    cluster_t first_cluster, uint32_t size);
+
+#endif
diff --git a/fatblock/fatblock.c b/fatblock/fatblock.c
new file mode 100644
index 0000000..dc9f402
--- /dev/null
+++ b/fatblock/fatblock.c
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <ublock/ublock.h>
+
+#include "errors.h"
+#include "import.h"
+#include "fs.h"
+#include "read.h"
+#include "utils.h"
+
+static struct fs fs;
+static struct ublock_ctx *ub;
+static int ums_lun = 0;
+
+static int fs_import(struct fs *fs, uint16_t cluster_size, offset_t data_size, offset_t *total_size_out) {
+	int ret;
+
+	ret = fs_init(fs, cluster_size, data_size, total_size_out);
+	if (ret)
+		return ret;
+
+	ret = import_tree(fs, ".");
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+
+
+static int read_callback(char *buf, uint64_t length, uint64_t offset) {
+	int result;
+	int i;
+
+	result = fs_read(&fs, buf, offset, length);
+	if (result == SKY_IS_FALLING) {
+		WARN("underlying filesystem has been modified; stopping.\n");
+		ublock_stop(ub);
+	}
+
+	return result ? -EINVAL : 0;
+}
+
+static int write_callback(const char *buf, uint64_t length, uint64_t offset) {
+	DEBUG("writing to (%llu, %llu): we are read-only\n", offset, length);
+
+	return -EINVAL;
+}
+
+static struct ublock_ops ops = {
+	.read = &read_callback,
+	.write = &write_callback
+};
+
+
+
+static int set_ums_file(int index)
+{
+	char filename[PATH_MAX];
+	FILE *file;
+
+	sprintf(filename, "/sys/devices/platform/usb_mass_storage/lun%d/file", ums_lun);
+	file = fopen(filename, "w");
+	if (!file) {
+		WARN("setting USB mass storage file: fopen(%s) failed: %s\n", filename, strerror(errno));
+		return -1;
+	}
+
+	WARN("writing '/dev/block/ublock%d' to %s.\n", index, filename);
+
+	fprintf(file, "/dev/block/ublock%d", index);
+
+	fclose(file);
+
+	return 0;
+}
+
+static int clear_ums_file(void)
+{
+	char filename[PATH_MAX];
+	FILE *file;
+
+	sprintf(filename, "/sys/devices/platform/usb_mass_storage/lun%d/file", ums_lun);
+	file = fopen(filename, "w");
+	if (!file) {
+		WARN("clearing USB mass storage file: fopen(%s) failed: %s\n", filename, strerror(errno));
+		return -1;
+	}
+
+	fclose(file);
+
+	return 0;
+}
+
+
+
+
+static void cleanup(void)
+{
+	WARN("cleanup: clearing USB mass storage file\n");
+	clear_ums_file();
+	WARN("cleanup: destroying block device\n");
+	ublock_destroy(ub);
+}
+
+static void signal_handler(int sig)
+{
+	WARN("received signal %d\n", sig);
+	cleanup();
+	exit(0);
+}
+
+static int normal_exit = 0;
+
+static void atexit_handler(void)
+{
+	if (normal_exit)
+		return;
+
+	cleanup();
+}
+
+int main(int argc, char *argv[]) {
+	char *path;
+	int mb;
+	offset_t total_size;
+	int index;
+	int ret;
+
+	signal(SIGINT, &signal_handler);
+	signal(SIGTERM, &signal_handler);
+	atexit(&atexit_handler);
+
+	if (argc != 3)
+		DIE("Usage: fatblock <path> <size in MB>\n");
+
+	path = argv[1];
+	mb = atoi(argv[2]);
+
+	INFO("fatblock: importing filesystem from %s (%d MB)\n", path, mb);
+
+	ret = chdir(path);
+	if (ret < 0)
+		DIE("fatblock: chdir(%s) failed: %s; aborting\n", path, strerror(errno));
+
+	ret = fs_import(&fs, 32768, 1048576LL * mb, &total_size);
+	if (ret)
+		DIE("fatblock: couldn't import filesystem; aborting\n");
+
+	INFO("fatblock: filesystem imported (%llu bytes)\n", total_size);
+
+	ret = ublock_init(&ub, &ops, total_size);
+	if (ret)
+		DIE("fatblock: couldn't create block device; aborting\n");
+	index = ublock_index(ub);
+	if (index < 0)
+		DIE("fatblock: invalid ublock index %d; aborting\n", index);
+
+	INFO("fatblock: block device ublock%d created\n", index);
+	set_ums_file(index);
+
+	INFO("fatblock: entering main loop\n");
+	ublock_run(ub);
+
+	INFO("fatblock: destroying block device\n");
+	clear_ums_file();
+	ublock_destroy(ub);
+
+	normal_exit = 1;
+
+	INFO("fatblock: goodbye!\n");
+	return 0;
+}
diff --git a/fatblock/fdpool.c b/fatblock/fdpool.c
new file mode 100644
index 0000000..900aeca
--- /dev/null
+++ b/fatblock/fdpool.c
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <utils.h>
+
+#include "fdpool.h"
+
+#define INVALID_FD (-1)
+#define FDPOOL_SIZE 4
+
+static struct pooled_fd fdpool_head = {
+	.fd = INVALID_FD,
+	.prev = &fdpool_head,
+	.next = &fdpool_head
+};
+static int fdpool_count = 0;
+
+static void fdpool_insert_head(struct pooled_fd *node) {
+	struct pooled_fd *prev = &fdpool_head;
+	struct pooled_fd *next = prev->next;
+
+	assert(node);
+
+	prev->next = node;
+	node->prev = prev;
+	node->next = next;
+	next->prev = node;
+
+	fdpool_count++;
+}
+
+static void fdpool_remove(struct pooled_fd *node) {
+	struct pooled_fd *prev = node->prev;
+	struct pooled_fd *next = node->next;
+
+	assert(prev);
+	assert(next);
+
+	prev->next = next;
+	next->prev = prev;
+
+	fdpool_count--;
+}
+
+static struct pooled_fd *fdpool_remove_tail(void) {
+	struct pooled_fd *tail = fdpool_head.prev;
+
+	assert(tail != &fdpool_head);
+
+	fdpool_remove(tail);
+
+	return tail;
+}
+
+static void fdpool_clear(struct pooled_fd *pfd) {
+	assert(pfd);
+
+	pfd->fd = INVALID_FD;
+	pfd->prev = pfd->next = NULL;
+}
+
+static void fdpool_unpool(struct pooled_fd *pfd) {
+	close(pfd->fd);
+	fdpool_clear(pfd);
+}
+
+static void fdpool_evict(void) {
+	struct pooled_fd *tail;
+
+	tail = fdpool_remove_tail();
+	fdpool_unpool(tail);
+}
+
+static void fdpool_pool(struct pooled_fd *pfd, int fd) {
+	if (fdpool_count >= FDPOOL_SIZE)
+		fdpool_evict();
+
+	fdpool_insert_head(pfd);
+	pfd->fd = fd;
+}
+
+static void fdpool_touch(struct pooled_fd *pfd) {
+	fdpool_remove(pfd);
+	fdpool_insert_head(pfd);
+}
+
+
+
+void fdpool_init(struct pooled_fd *pfd) {
+	fdpool_clear(pfd);
+}
+
+int fdpool_open(struct pooled_fd *pfd, const char *pathname, int flags) {
+	int open_errno;
+	int fd;
+
+	if (pfd->fd != INVALID_FD) {
+		fdpool_touch(pfd);
+		return pfd->fd;
+	}
+
+	fd = open(pathname, flags);
+	open_errno = errno;
+
+	if (fd >= 0) {
+		fdpool_pool(pfd, fd);
+	}
+
+	errno = open_errno;
+	return fd;
+}
+
+void fdpool_close(struct pooled_fd *pfd) {
+	assert(pfd);
+
+	fdpool_unpool(pfd);
+}
diff --git a/fatblock/fdpool.h b/fatblock/fdpool.h
new file mode 100644
index 0000000..85c7af3
--- /dev/null
+++ b/fatblock/fdpool.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FDPOOL_H
+#define FDPOOL_H
+
+struct pooled_fd {
+  struct pooled_fd *prev;
+  struct pooled_fd *next;
+  int fd;
+};
+
+void fdpool_init(struct pooled_fd *pfd);
+int fdpool_open(struct pooled_fd *pfd, const char *pathname, int flags);
+void fdpool_close(struct pooled_fd *pfd);
+
+#endif
diff --git a/fatblock/filedir.h b/fatblock/filedir.h
new file mode 100644
index 0000000..2c95b50
--- /dev/null
+++ b/fatblock/filedir.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FILEDIR_H
+#define FILEDIR_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "extent.h"
+#include "fdpool.h"
+#include "types.h"
+
+struct file {
+	struct extent extent;
+
+	char *path;
+	uint32_t size;
+	cluster_t first_cluster;
+
+	dev_t dev;
+	ino_t ino;
+	time_t mtime;
+
+	struct pooled_fd pfd;
+};
+
+struct dir {
+	struct extent extent;
+
+	char *path;
+	uint32_t size;
+	cluster_t first_cluster;
+
+	struct fat_dirent *entries;
+};
+
+#endif
diff --git a/fatblock/fs.c b/fatblock/fs.c
new file mode 100644
index 0000000..b603b13
--- /dev/null
+++ b/fatblock/fs.c
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <sys/endian.h>
+
+#include "extent.h"
+#include "errors.h"
+#include "fat.h"
+#include "fs.h"
+#include "types.h"
+#include "utils.h"
+
+#define DEFAULT_SECTOR_SIZE 512
+
+static void fs_add_extent(struct fs *fs, struct extent *extent,
+                          offset_t start, offset_t len, int type)
+{
+	assert(fs);
+	assert(extent);
+
+	extent->start = start;
+	extent->len = len;
+	extent->type = type;
+
+	extent->next = fs->extents;
+	fs->extents = extent;
+}
+
+struct extent *fs_find_extent(struct fs *fs, offset_t start, offset_t len,
+                              struct extent *last,
+                              offset_t *r_start_out,
+                              offset_t *e_start_out,
+                              offset_t *len_out)
+{
+	struct extent *e;
+	offset_t end;
+	offset_t e_start, e_end, e_len, e_rel_start, r_rel_start, rel_len;
+
+	assert(fs);
+
+	end = start + len;
+
+	e = last ? last->next : fs->extents;
+	for (; e; e = e->next) {
+		e_start = e->start;
+		e_len = e->len;
+		e_end = e_start + e_len;
+
+		if (start >= e_end)
+			continue;
+
+		if (end <= e_start)
+			continue;
+
+		if (e_start <= start) {
+			r_rel_start = 0;
+			e_rel_start = start - e_start;
+			if (end <= e_end)
+				rel_len = len;
+			else
+				rel_len = e_end - start;
+		} else {
+			e_rel_start = 0;
+			r_rel_start = e_start - start;
+			if (e_end <= end)
+				rel_len = e_len;
+			else
+				rel_len = end - e_start;
+		}
+
+		assert(e_rel_start < e_len);
+		assert(e_rel_start + rel_len <= e_len);
+		assert(r_rel_start < len);
+		assert(r_rel_start + rel_len <= len);
+
+		if (r_start_out)
+			*r_start_out = r_rel_start;
+		if (e_start_out)
+			*e_start_out = e_rel_start;
+		if (len_out)
+			*len_out = rel_len;
+
+		return e;
+	}
+
+	return NULL;
+}
+
+static void fs_set_fat(struct fs *fs, cluster_t cluster, fat_entry_t entry)
+{
+	assert(fs);
+
+	fs->fat[cluster] = htole32(entry);
+}
+
+int fs_alloc_extent(struct fs *fs, struct extent *extent,
+                    offset_t len, int type, cluster_t *first_cluster_out)
+{
+	assert(fs);
+	assert(extent);
+
+	cluster_t clusters_needed, start;
+	cluster_t i;
+
+	if (len == 0) {
+		extent->start = 0;
+		extent->len = 0;
+		extent->type = type;
+		*first_cluster_out = 0;
+		return 0;
+	}
+
+	clusters_needed = (len + fs->cluster_size - 1) / fs->cluster_size;
+
+	/* Check for adequate space. */
+	if (fs->next_cluster + clusters_needed > fs->num_clusters) {
+		WARN("allocating extent: filesystem is full!\n");
+		return -1;
+	}
+
+	/* Allocate clusters. */
+	start = fs->next_cluster;
+	fs->next_cluster += clusters_needed;
+
+	/* Update FAT. */
+	for (i = 0; i < clusters_needed - 1; i++) {
+		fs_set_fat(fs, start + i, start + i + 1);
+	}
+	fs_set_fat(fs, start + clusters_needed - 1, FAT_ENTRY_EOC);
+
+	*first_cluster_out = start;
+
+	fs_add_extent(fs,
+                      extent,
+                      fs->data_offset + (offset_t)(start - FAT_CLUSTER_ZERO)
+                                        * fs->cluster_size,
+                      (offset_t)clusters_needed * fs->cluster_size,
+                      type);
+
+	return 0;
+}
+
+int fs_init(struct fs *fs, uint16_t cluster_size, offset_t data_size, offset_t *total_size_out)
+{
+	uint16_t sector_size;
+	cluster_t data_clusters;
+	sector_t reserved_sectors, fat_sectors, data_sectors, total_sectors;
+	sector_t sectors_per_cluster;
+	int fat_entries_per_sector;
+	fat_entry_t *fat;
+	struct fat_boot_sector *bs;
+	struct fat_info_sector *is;
+
+	assert(fs);
+
+	sector_size = DEFAULT_SECTOR_SIZE;
+	fs->cluster_size = cluster_size;
+
+	sectors_per_cluster = cluster_size / DEFAULT_SECTOR_SIZE;
+	fat_entries_per_sector = sector_size / sizeof(fat_entry_t);
+
+	data_clusters = (data_size + cluster_size - 1) / cluster_size;
+	data_sectors = data_clusters * sectors_per_cluster;
+	fat_sectors = ((data_clusters + 2) + fat_entries_per_sector - 1)
+                      / fat_entries_per_sector;
+	reserved_sectors = 3;
+	total_sectors = reserved_sectors + fat_sectors + data_sectors;
+
+	memset(&fs->boot, 0, sizeof(fs->boot));
+	bs = &fs->boot;
+
+	strpadcpy(bs->name, "FATBLOCK", ' ', sizeof(bs->name));
+	bs->sector_size = htole16(sector_size);
+	bs->sectors_per_cluster = sectors_per_cluster;
+	bs->reserved_sectors = htole16(reserved_sectors);
+	bs->fats = 1;
+	bs->media_desc = FAT_MEDIA_DESC_FIXED;
+	/* TODO: Calculate geometry? */
+	bs->sectors_per_track = htole16(42);
+	bs->heads = htole16(42);
+	bs->sectors32 = htole32(total_sectors);
+	bs->fat_sectors32 = htole32(fat_sectors);
+	/* bs->rootdir_start will be set later. */
+	bs->fs_info_sector = htole16(1);
+	bs->backup_boot_sector = htole16(2);
+	bs->phys_drive = FAT_PHYS_DRIVE_REMOVABLE;
+	bs->ext_boot_sig = FAT_EXT_BOOT_SIG;
+	bs->serial = 0x42424242;
+	strpadcpy(bs->vol_label, "FATBLOCK", ' ', sizeof(bs->vol_label));
+	strpadcpy(bs->type, "FAT32", ' ', sizeof(bs->type));
+	memcpy(bs->boot_sig, FAT_BOOT_SIG, sizeof(bs->boot_sig));
+
+	memset(&fs->info, 0, sizeof(fs->info));
+	is = &fs->info;
+
+	memcpy(is->info_sig1, FAT_INFO_SIG1, sizeof(is->info_sig1));
+	memcpy(is->info_sig2, FAT_INFO_SIG2, sizeof(is->info_sig2));
+	is->free_clusters = htole32(-1);
+	is->last_cluster = htole32(FAT_CLUSTER_ZERO);
+	memcpy(is->info_sig3, FAT_INFO_SIG3, sizeof(is->info_sig3));
+
+	fs->num_clusters = FAT_CLUSTER_ZERO + data_clusters;
+	fs->next_cluster = FAT_CLUSTER_ZERO;
+
+	fs->fat_size = fat_sectors * sector_size;
+	fs->fat = malloc(fs->fat_size);
+	if (!fs->fat) {
+		WARN("initializing filesystem: couldn't allocate FAT extent: out of memory\n");
+		return MALLOC_FAIL;
+	}
+	memset(fs->fat, 0, fs->fat_size);
+
+	fs->data_offset = (reserved_sectors + fat_sectors) * sector_size;
+
+	fs->extents = NULL;
+	fs_add_extent(fs, &fs->boot_extent,
+                      0, sector_size,
+                      EXTENT_TYPE_BOOT);
+	fs_add_extent(fs, &fs->info_extent,
+                      sector_size, sector_size,
+                      EXTENT_TYPE_INFO);
+	fs_add_extent(fs, &fs->backup_boot_extent,
+                      2 * sector_size, sector_size,
+                      EXTENT_TYPE_BOOT);
+	fs_add_extent(fs, &fs->fat_extent,
+                      reserved_sectors * sector_size, fs->fat_size,
+                      EXTENT_TYPE_FAT);
+
+	*total_size_out = (offset_t)total_sectors * sector_size;
+
+	return 0;
+}
+
+void fs_set_rootdir_start(struct fs *fs, cluster_t rootdir_start)
+{
+	assert(fs);
+
+	fs->boot.rootdir_start = htole32(rootdir_start);
+}
+
+void fs_update_free_clusters(struct fs *fs)
+{
+	assert(fs);
+
+	fs->info.free_clusters = htole32(fs->num_clusters - fs->next_cluster);
+}
diff --git a/fatblock/fs.h b/fatblock/fs.h
new file mode 100644
index 0000000..c58decc
--- /dev/null
+++ b/fatblock/fs.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FS_H
+#define FS_H
+
+#include "extent.h"
+#include "fat.h"
+#include "types.h"
+
+struct fs {
+	uint16_t cluster_size;
+
+	cluster_t num_clusters;
+	cluster_t next_cluster;
+	struct extent *extents;
+
+	struct fat_boot_sector boot;
+	struct extent boot_extent;
+	struct extent backup_boot_extent;
+
+	struct fat_info_sector info;
+	struct extent info_extent;
+
+	struct extent fat_extent;
+	fat_entry_t *fat;
+	offset_t fat_size;
+
+	offset_t data_offset;
+};
+
+
+int fs_alloc_extent(struct fs *fs, struct extent *extent,
+                    offset_t len, int type, cluster_t *first_cluster_out);
+struct extent *fs_find_extent(struct fs *fs, offset_t start, offset_t len, struct extent *last,
+                              offset_t *r_start_out, offset_t *e_start_out, offset_t *len_out);
+int fs_init(struct fs *fs, uint16_t cluster_size, offset_t data_size, offset_t *total_size_out);
+void fs_set_rootdir_start(struct fs *fs, cluster_t rootdir_start);
+void fs_update_free_clusters(struct fs *fs);
+
+#endif
diff --git a/fatblock/import.c b/fatblock/import.c
new file mode 100644
index 0000000..4697819
--- /dev/null
+++ b/fatblock/import.c
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "errors.h"
+#include "extent.h"
+#include "fat.h"
+#include "fdpool.h"
+#include "filedir.h"
+#include "fs.h"
+#include "import.h"
+#include "utils.h"
+
+static inline int valid_char(int c)
+{
+	return (isalnum(c) || strchr("!#$%'()-@^_`{}~", c) || ((c >= 128) && (c < 256)));
+}
+
+static int convert_name(char *short_name, const char *long_name)
+{
+	int i;
+
+	const char *s;
+	const char *dot;
+	int c;
+
+	dot = NULL;
+
+	for (s = long_name; *s; s++) {
+		if (*s == '.') {
+			if (dot) {
+				goto short_fail;
+			} else {
+				dot = s;
+			}
+		} else if (!valid_char(*s)) {
+			goto short_fail;
+		}
+	}
+
+	if (dot - long_name > 8) {
+		goto short_fail;
+	}
+
+	if (dot && (s - (dot + 1) > 3)) {
+		goto short_fail;
+	}
+
+	memset(short_name, ' ', 11);
+
+	if (!dot) {
+		dot = s;
+	}
+
+	for (i = 0; i < dot - long_name; i++) {
+		short_name[i] = toupper(long_name[i]);
+	}
+
+	for (i = 0; i < s - dot; i++) {
+		short_name[8 + i] = toupper(dot[1 + i]);
+	}
+
+	return 0;
+
+short_fail:
+	return 1;
+}
+
+struct imported {
+	cluster_t first_cluster;
+	uint32_t size;
+	struct fat_dirent *dot_dot_dirent;
+};
+
+static int import_file(struct fs *fs, char *path, struct imported *out)
+{
+	struct stat st;
+	struct file *f = NULL;
+	char *path_copy = NULL;
+	int ret;
+
+	ret = stat(path, &st);
+	if (ret < 0) {
+		WARN("importing %s: stat failed: %s\n", path, strerror(errno));
+		goto fail;
+	}
+
+	f = malloc(sizeof(struct file));
+	if (!f) {
+		WARN("importing %s: couldn't allocate file struct: out of memory\n", path);
+		ret = MALLOC_FAIL;
+		goto fail;
+	}
+
+	path_copy = strdup(path);
+	if (!path_copy) {
+		WARN("importing %s: couldn't strdup path: out of memory\n", path);
+		ret = MALLOC_FAIL;
+		goto fail;
+	}
+
+	f->path = path_copy;
+	f->size = st.st_size;
+	f->dev = st.st_dev;
+	f->ino = st.st_ino;
+	f->mtime = st.st_mtime;
+	fdpool_init(&f->pfd);
+
+	ret = fs_alloc_extent(fs, &f->extent,
+                              f->size, EXTENT_TYPE_FILE, &f->first_cluster);
+	if (ret) {
+		WARN("importing %s: couldn't allocate data extent\n", path);
+		goto fail;
+	}
+
+	out->first_cluster = f->first_cluster;
+	out->size = f->size;
+	out->dot_dot_dirent = NULL;
+
+	return 0;
+
+fail:
+	if (path_copy)
+		free(path_copy);
+	if (f)
+		free(f);
+	return ret;
+}
+
+struct item {
+	char name[11];
+	struct imported imp;
+	struct item *next;
+	int is_dir;
+};
+
+static struct item *free_items_head;
+
+static struct item *alloc_item(void)
+{
+	struct item *item;
+
+	if (free_items_head) {
+		item = free_items_head;
+		free_items_head = item->next;
+	} else {
+		item = malloc(sizeof(struct item));
+		/* May return NULL if item couldn't be allocated. */
+	}
+
+	return item;
+}
+
+static void free_item(struct item *item)
+{
+	item->next = free_items_head;
+	free_items_head = item;
+}
+
+static void free_items(struct item *head)
+{
+	struct item *tail;
+
+	for (tail = head; tail->next; tail = tail->next);
+
+	tail->next = free_items_head;
+	free_items_head = head;
+}
+
+/* TODO: With some work, this can be rewritten so we don't recurse
+ * until all memory is allocated. */
+static int import_dir(struct fs *fs, char *path, int is_root, struct imported *out)
+{
+	struct dir *d;
+	cluster_t first_cluster;
+
+	DIR *dir;
+	struct dirent *de;
+
+	char ch_path[PATH_MAX];
+	struct imported *ch_imp;
+	cluster_t ch_first_cluster;
+	struct fat_dirent *ch_dirent;
+
+	int ret;
+
+	struct item *items;
+	struct item *item;
+	int count;
+
+	int i;
+
+	dir = opendir(path);
+	if (!dir) {
+		WARN("importing %s: opendir failed: %s\n", path, strerror(errno));
+		return -1;
+	}
+
+	d = malloc(sizeof(struct dir));
+	if (!d) {
+		WARN("importing %s: couldn't allocate dir struct: out of memory\n", path);
+		closedir(dir);
+		return MALLOC_FAIL;
+	}
+
+	d->path = strdup(path);
+	if (!d->path) {
+		WARN("importing %s: couldn't strdup path: out of memory\n", path);
+		closedir(dir);
+		free(d);
+		return MALLOC_FAIL;
+	}
+
+	items = NULL;
+	item = NULL;
+	count = 0;
+
+	while ((de = readdir(dir))) {
+		if (de->d_name[0] == '.') {
+			goto skip_item;
+		}
+
+		ret = snprintf(ch_path, PATH_MAX, "%s/%s", path, de->d_name);
+		if (ret < 0 || ret >= PATH_MAX) {
+			goto skip_item;
+		}
+
+		item = alloc_item();
+		if (!item) {
+			WARN("importing %s: couldn't allocate item struct: out of memory\n", path);
+			ret = MALLOC_FAIL;
+			goto free_items;
+		}
+
+		if (convert_name(item->name, de->d_name)) {
+			goto skip_item;
+		}
+
+		switch (de->d_type) {
+			case DT_REG:
+				import_file(fs, ch_path, &item->imp);
+				item->is_dir = 0;
+				break;
+			case DT_DIR:
+				import_dir(fs, ch_path, 0, &item->imp);
+				item->is_dir = 1;
+				break;
+			default:
+				goto skip_item;
+		}
+
+		item->next = items;
+		items = item;
+
+		count++;
+
+		item = NULL;
+
+		continue;
+
+skip_item:
+		if (item)
+			free_item(item);
+	}
+
+	closedir(dir);
+
+	d->size = sizeof(struct fat_dirent) * (count + (is_root ? 0 : 2));
+	ret = fs_alloc_extent(fs, &d->extent, d->size, EXTENT_TYPE_DIR, &d->first_cluster);
+	if (ret) {
+		WARN("importing %s: couldn't allocate directory table extent: out of space\n", path);
+		goto free_items;
+	}
+
+	first_cluster = is_root ? 0 : d->first_cluster;
+
+	d->entries = malloc(sizeof(struct fat_dirent) * (count + (is_root ? 0 : 2)));
+	assert(d->entries);
+	for (i = count - 1; i >= 0; i--) {
+		item = items;
+		items = item->next;
+
+		ch_dirent = &d->entries[i + (is_root ? 0 : 2)];
+
+		fat_dirent_set(ch_dirent,
+                               item->name, item->is_dir ? FAT_ATTR_SUBDIR : 0,
+                               item->imp.first_cluster, item->imp.size);
+
+		if (item->imp.dot_dot_dirent) {
+			fat_dirent_set_first_cluster(item->imp.dot_dot_dirent, first_cluster);
+		}
+
+		free_item(item);
+	}
+
+	if (!is_root) {
+		fat_dirent_set(&d->entries[0],
+                               "..         ", FAT_ATTR_SUBDIR,
+                               (cluster_t)-1, 0);
+		out->dot_dot_dirent = &d->entries[0]; /* will set first_cluster */
+
+		fat_dirent_set(&d->entries[1],
+                               ".          ", FAT_ATTR_SUBDIR,
+                               first_cluster, 0);
+	} else {
+		out->dot_dot_dirent = NULL;
+	}
+
+	out->first_cluster = d->first_cluster;
+	out->size = 0;
+
+	return 0;
+
+free_items:
+	free_items(items);
+	free(d->path);
+	free(d);
+
+	return ret;
+}
+
+int import_tree(struct fs *fs, char *path)
+{
+	struct imported imp;
+	int ret;
+
+	ret = import_dir(fs, path, 0, &imp);
+	if (ret)
+		return ret;
+
+	fs_set_rootdir_start(fs, imp.first_cluster);
+	fs_update_free_clusters(fs);
+
+	return 0;
+}
diff --git a/fatblock/import.h b/fatblock/import.h
new file mode 100644
index 0000000..e1f90c4
--- /dev/null
+++ b/fatblock/import.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IMPORT_H
+#define IMPORT_H
+
+#include "types.h"
+
+int import_tree(struct fs *fs, char *path);
+
+#endif
diff --git a/fatblock/read.c b/fatblock/read.c
new file mode 100644
index 0000000..ca7deea
--- /dev/null
+++ b/fatblock/read.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "errors.h"
+#include "filedir.h"
+#include "fs.h"
+#include "utils.h"
+
+static int buffer_read(char *buf, offset_t buf_len, char *out, offset_t off, offset_t len) {
+	assert(buf);
+	assert(out);
+
+	if (off >= buf_len) {
+		memset(out, 0, len);
+		return 0;
+	}
+
+	if (off + len > buf_len) {
+		memset(out + (buf_len - off), 0, len - (buf_len - off));
+		len = buf_len - off;
+	}
+
+	assert(off < buf_len);
+	assert(off + len <= buf_len);
+
+	memcpy(out, buf + off, len);
+
+	return 0;
+}
+
+static int file_check_metadata(struct file *f) {
+	struct stat st;
+	int ret;
+
+	assert(f);
+
+	ret = stat(f->path, &st);
+	if (ret) {
+		WARN("checking metadata of %s: stat failed: %s\n", f->path, strerror(errno));
+		return -1;
+	}
+
+	if (f->mtime != st.st_mtime)
+		return -1;
+
+	return 0;
+}
+
+static int file_read(struct file *f, char *buf, offset_t off, offset_t len) {
+	int fd;
+	off_t sought;
+	ssize_t ret;
+
+	assert(f);
+	assert(buf);
+
+	if (off >= UINT32_MAX) {
+		WARN("reading %s (%llu, %llu): ignoring read that starts past 2^32\n", f->path, off, len);
+		return 0;
+	}
+
+	if (off + len > UINT32_MAX) {
+		WARN("reading %s (%llu, %llu): truncating read that ends past 2^32\n", f->path, off, len);
+		len = UINT32_MAX - off;
+	}
+
+	if (file_check_metadata(f)) {
+		WARN("reading %s (%llu, %llu): metadata has changed\n", f->path, off, len);
+		return SKY_IS_FALLING;
+	}
+
+	fd = fdpool_open(&f->pfd, f->path, O_RDONLY);
+	if (fd < 0) {
+		WARN("reading %s: open failed: %s\n", f->path, strerror(errno));
+		return -1;
+	}
+
+	sought = lseek(fd, (off_t)len, SEEK_SET);
+	if (sought != (off_t)len) {
+		WARN("reading %s (%llu, %llu): seek failed: %s\n", f->path, off, len, strerror(errno));
+		return -1;
+	}
+
+	ret = read(fd, buf, (size_t)len);
+	if (ret != (ssize_t)len) {
+		WARN("reading %s (%llu, %llu): read failed: %s\n", f->path, off, len, strerror(errno));
+		return -1;
+	}
+
+	/* leave fd open; fdpool will close it if needed. */
+
+	return 0;
+}
+
+static int dir_read(struct dir *d, char *buf, offset_t off, offset_t len) {
+	assert(d);
+	assert(buf);
+
+	return buffer_read((char*)d->entries, d->size, buf, off, len);
+}
+
+static int extent_read(struct fs *fs, struct extent *e, char *buf, offset_t off, offset_t len) {
+	assert(fs);
+	assert(e);
+	assert(buf);
+
+	switch (e->type) {
+	case EXTENT_TYPE_BOOT:
+		return buffer_read((char*)&fs->boot, sizeof(fs->boot), buf, off, len);
+	case EXTENT_TYPE_INFO:
+		return buffer_read((char*)&fs->info, sizeof(fs->info), buf, off, len);
+	case EXTENT_TYPE_FAT:
+		return buffer_read((char*)fs->fat, fs->fat_size, buf, off, len);
+	case EXTENT_TYPE_FILE:
+		return file_read((struct file *)e, buf, off, len);
+	case EXTENT_TYPE_DIR:
+		return dir_read((struct dir *)e, buf, off, len);
+	default:
+		WARN("reading extent: unexpected type %d\n", e->type);
+		return -1;
+	}
+}
+
+int fs_read(struct fs *fs, char *buf, offset_t start, offset_t len) {
+	struct extent *e = NULL;
+	offset_t e_start, r_start, rel_len;
+	int ret;
+
+	memset(buf, 0, len);
+
+	while ((e = fs_find_extent(fs, start, len, e, &r_start, &e_start, &rel_len))) {
+		ret = extent_read(fs, e, buf + r_start, e_start, rel_len);
+		if (ret == SKY_IS_FALLING)
+			return SKY_IS_FALLING;
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
diff --git a/fatblock/read.h b/fatblock/read.h
new file mode 100644
index 0000000..f5f68a1
--- /dev/null
+++ b/fatblock/read.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef READ_H
+#define READ_H
+
+#include "types.h"
+
+struct fs;
+
+int fs_read(struct fs *fs, char *buf, offset_t start, offset_t len);
+
+#endif
diff --git a/fatblock/types.h b/fatblock/types.h
new file mode 100644
index 0000000..4119039
--- /dev/null
+++ b/fatblock/types.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef TYPES_H
+#define TYPES_H
+
+#include <stdint.h>
+
+struct fs;
+
+typedef uint64_t offset_t;
+typedef uint32_t cluster_t;
+
+#endif
diff --git a/fatblock/utils.c b/fatblock/utils.c
new file mode 100644
index 0000000..20fa83b
--- /dev/null
+++ b/fatblock/utils.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "utils.h"
+
+void strpadcpy(char *d, const char *s, char p, size_t l)
+{
+	while (*s && l-- > 0)
+		*d++ = *s++;
+
+	while (l-- > 0)
+		*d++ = ' ';
+}
+
+void warn(char *msg)
+{
+	fprintf(stderr, "%s", msg);
+}
+
+void die(char *msg)
+{
+	fprintf(stderr, "%s", msg);
+	exit(EXIT_FAILURE);
+}
diff --git a/fatblock/utils.h b/fatblock/utils.h
new file mode 100644
index 0000000..7fe4b88
--- /dev/null
+++ b/fatblock/utils.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define EPRINT(...) fprintf(stderr, __VA_ARGS__)
+#define DIE(...) do { EPRINT(__VA_ARGS__); exit(EXIT_FAILURE); } while (0)
+#define WARN(...) EPRINT(__VA_ARGS__)
+#define INFO(...) EPRINT(__VA_ARGS__)
+#define DEBUG(...) EPRINT(__VA_ARGS__)
+
+void strpadcpy(char *d, const char *s, char p, size_t l);
+void warn(char *msg);
+void die(char *msg);
+
+#endif