Index: boot/Makefile.common
===================================================================
--- boot/Makefile.common	(revision 692be1ae416d773be35a842131ca027484d54344)
+++ boot/Makefile.common	(revision 4b164223422e3305e9d3dafd0a78d8aca4c766f3)
@@ -149,4 +149,5 @@
 
 RD_APPS_NON_ESSENTIAL = \
+	$(USPACE_PATH)/app/bithenge/bithenge \
 	$(USPACE_PATH)/app/blkdump/blkdump \
 	$(USPACE_PATH)/app/bnchmark/bnchmark \
Index: uspace/Makefile
===================================================================
--- uspace/Makefile	(revision 692be1ae416d773be35a842131ca027484d54344)
+++ uspace/Makefile	(revision 4b164223422e3305e9d3dafd0a78d8aca4c766f3)
@@ -35,4 +35,5 @@
 DIRS = \
 	app/bdsh \
+	app/bithenge \
 	app/blkdump \
 	app/bnchmark \
Index: uspace/app/bithenge/Makefile
===================================================================
--- uspace/app/bithenge/Makefile	(revision 4b164223422e3305e9d3dafd0a78d8aca4c766f3)
+++ uspace/app/bithenge/Makefile	(revision 4b164223422e3305e9d3dafd0a78d8aca4c766f3)
@@ -0,0 +1,39 @@
+#
+# Copyright (c) 2012 Sean Bartell
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# - Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+# - The name of the author may not be used to endorse or promote products
+#   derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+USPACE_PREFIX = ../..
+LIBS = $(LIBBLOCK_PREFIX)/libblock.a
+EXTRA_CFLAGS = -I$(LIBBLOCK_PREFIX)
+BINARY = bithenge
+
+SOURCES = \
+	blob.c \
+	block.c \
+	test.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/app/bithenge/blob.c
===================================================================
--- uspace/app/bithenge/blob.c	(revision 4b164223422e3305e9d3dafd0a78d8aca4c766f3)
+++ uspace/app/bithenge/blob.c	(revision 4b164223422e3305e9d3dafd0a78d8aca4c766f3)
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2012 Sean Bartell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup bithenge
+ * @{
+ */
+/**
+ * @file
+ * Raw binary blobs.
+ */
+
+#include <assert.h>
+#include <bool.h>
+#include <errno.h>
+#include <macros.h>
+#include <mem.h>
+#include <stdlib.h>
+#include "blob.h"
+
+/** Initialize a random access blob.
+ * @memberof bithenge_blob_t
+ * @param[out] blob The blob to initialize.
+ * @param[in] ops Operations providing random access support. This pointer must
+ * be valid until the blob is destroyed.
+ * @return EOK on success or an error code from errno.h.
+ */
+int bithenge_new_random_access_blob(bithenge_blob_t *blob,
+    const bithenge_random_access_blob_ops_t *ops)
+{
+	assert(blob);
+	assert(ops);
+	assert(ops->destroy);
+	assert(ops->read);
+	assert(ops->size);
+
+	blob->ops = ops;
+	return EOK;
+}
+
+static int sequential_buffer(bithenge_sequential_blob_t *blob, aoff64_t end)
+{
+	bool need_realloc = false;
+	while (end > blob->buffer_size) {
+		blob->buffer_size = max(4096, 2 * blob->buffer_size);
+		need_realloc = true;
+	}
+	if (need_realloc) {
+		char *buffer = realloc(blob->buffer, blob->buffer_size);
+		if (!buffer)
+			return ENOMEM;
+		blob->buffer = buffer;
+	}
+	aoff64_t size = end - blob->data_size;
+	int rc = blob->ops->read(blob, blob->buffer + blob->data_size, &size);
+	if (rc != EOK)
+		return rc;
+	blob->data_size += size;
+	return EOK;
+}
+
+static inline bithenge_sequential_blob_t *sequential_from_blob(
+    bithenge_blob_t *base)
+{
+	return (bithenge_sequential_blob_t *)base;
+}
+
+static inline bithenge_blob_t *blob_from_sequential(
+    bithenge_sequential_blob_t *blob)
+{
+	return &blob->base;
+}
+
+static int sequential_size(bithenge_blob_t *base, aoff64_t *size)
+{
+	bithenge_sequential_blob_t *blob = sequential_from_blob(base);
+	int rc;
+	if (blob->ops->size) {
+		rc = blob->ops->size(blob, size);
+		if (rc == EOK)
+			return EOK;
+	}
+	rc = sequential_buffer(blob, blob->buffer_size);
+	if (rc != EOK)
+		return rc;
+	while (blob->data_size == blob->buffer_size) {
+		rc = sequential_buffer(blob, 2 * blob->buffer_size);
+		if (rc != EOK)
+			return rc;
+	}
+	*size = blob->data_size;
+	return EOK;
+}
+
+static int sequential_read(bithenge_blob_t *base, aoff64_t offset,
+    char *buffer, aoff64_t *size)
+{
+	bithenge_sequential_blob_t *blob = sequential_from_blob(base);
+	aoff64_t end = offset + *size;
+	if (end > blob->data_size) {
+		int rc = sequential_buffer(blob, end);
+		if (rc != EOK)
+			return rc;
+	}
+	if (offset > blob->data_size)
+		return EINVAL;
+	*size = min(*size, blob->data_size - end);
+	memcpy(buffer, blob->buffer + offset, *size);
+	return EOK;
+}
+
+static int sequential_destroy(bithenge_blob_t *base)
+{
+	bithenge_sequential_blob_t *blob = sequential_from_blob(base);
+	free(blob->buffer);
+	return blob->ops->destroy(blob);
+}
+
+static const bithenge_random_access_blob_ops_t sequential_ops = {
+	.size = sequential_size,
+	.read = sequential_read,
+	.destroy = sequential_destroy,
+};
+
+/** Initialize a sequential blob.
+ * @memberof bithenge_sequential_blob_t
+ * @param[out] blob The blob to initialize.
+ * @param[in] ops Operations providing sequential access support. This pointer
+ * must be valid until the blob is destroyed.
+ * @return EOK on success or an error code from errno.h.
+ */
+int bithenge_new_sequential_blob(bithenge_sequential_blob_t *blob,
+    const bithenge_sequential_blob_ops_t *ops)
+{
+	assert(blob);
+	assert(ops);
+	assert(ops->destroy);
+	assert(ops->read);
+	// ops->size is optional
+
+	int rc = bithenge_new_random_access_blob(blob_from_sequential(blob),
+	    &sequential_ops);
+	if (rc != EOK)
+		return rc;
+	blob->ops = ops;
+	blob->buffer = NULL; // realloc(NULL, ...) works like malloc
+	blob->buffer_size = 0;
+	blob->data_size = 0;
+	return EOK;
+}
+
+typedef struct {
+	bithenge_blob_t base;
+	const char *buffer;
+	size_t size;
+	bool needs_free;
+} memory_blob_t;
+
+static inline memory_blob_t *memory_from_blob(bithenge_blob_t *base)
+{
+	return (memory_blob_t *)base;
+}
+
+static inline bithenge_blob_t *blob_from_memory(memory_blob_t *blob)
+{
+	return &blob->base;
+}
+
+static int memory_size(bithenge_blob_t *base, aoff64_t *size)
+{
+	memory_blob_t *blob = memory_from_blob(base);
+	*size = blob->size;
+	return EOK;
+}
+
+static int memory_read(bithenge_blob_t *base, aoff64_t offset, char *buffer,
+    aoff64_t *size)
+{
+	memory_blob_t *blob = memory_from_blob(base);
+	if (offset > blob->size)
+		return ELIMIT;
+	*size = min(*size, blob->size - offset);
+	memcpy(buffer, blob->buffer + offset, *size);
+	return EOK;
+}
+
+static int memory_destroy(bithenge_blob_t *base)
+{
+	memory_blob_t *blob = memory_from_blob(base);
+	if (blob->needs_free)
+		free(blob->buffer);
+	free(blob);
+	return EOK;
+}
+
+static const bithenge_random_access_blob_ops_t memory_ops = {
+	.size = memory_size,
+	.read = memory_read,
+	.destroy = memory_destroy,
+};
+
+/** Create a blob from data. Unlike with @a
+ * bithenge_blob_t::bithenge_new_blob_from_buffer, the data is copied into a
+ * new buffer and the original data can be changed after this call. The blob
+ * must be freed with @a bithenge_blob_t::bithenge_blob_destroy after it is
+ * used.
+ * @memberof bithenge_blob_t
+ * @param[out] out Stores the created blob.
+ * @param[in] data The data.
+ * @param len The length of the data.
+ * @return EOK on success or an error code from errno.h. */
+int bithenge_new_blob_from_data(bithenge_blob_t **out, const void *data,
+    size_t len)
+{
+	int rc;
+	assert(data || !len);
+
+	memory_blob_t *blob = malloc(sizeof(*blob));
+	if (!blob)
+		return ENOMEM;
+	rc = bithenge_new_random_access_blob(blob_from_memory(blob),
+	    &memory_ops);
+	if (rc != EOK) {
+		free(blob);
+		return rc;
+	}
+	char *buffer = malloc(len);
+	if (!buffer) {
+		free(blob);
+		return rc;
+	}
+	memcpy(buffer, data, len);
+	blob->buffer = buffer;
+	blob->size = len;
+	blob->needs_free = true;
+	*out = blob_from_memory(blob);
+	return EOK;
+}
+
+/** Create a blob from a buffer. The buffer must exist as long as the blob
+ * does. The blob must be freed with @a bithenge_blob_t::bithenge_blob_destroy
+ * after it is used.
+ * @memberof bithenge_blob_t
+ * @param[out] out Stores the created blob.
+ * @param[in] buffer The buffer, which must not be changed until the blob is
+ * destroyed.
+ * @param len The length of the data.
+ * @param needs_free Whether the buffer should be freed with free() when the
+ * blob is destroyed.
+ * @return EOK on success or an error code from errno.h. */
+int bithenge_new_blob_from_buffer(bithenge_blob_t **out, const void *buffer,
+    size_t len, bool needs_free)
+{
+	int rc;
+	assert(buffer || !len);
+
+	memory_blob_t *blob = malloc(sizeof(*blob));
+	if (!blob)
+		return ENOMEM;
+	rc = bithenge_new_random_access_blob(blob_from_memory(blob),
+	    &memory_ops);
+	if (rc != EOK) {
+		free(blob);
+		return rc;
+	}
+	blob->buffer = buffer;
+	blob->size = len;
+	blob->needs_free = needs_free;
+	*out = blob_from_memory(blob);
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/app/bithenge/blob.h
===================================================================
--- uspace/app/bithenge/blob.h	(revision 4b164223422e3305e9d3dafd0a78d8aca4c766f3)
+++ uspace/app/bithenge/blob.h	(revision 4b164223422e3305e9d3dafd0a78d8aca4c766f3)
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2012 Sean Bartell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup bithenge
+ * @{
+ */
+/**
+ * @file
+ * Raw binary blobs.
+ */
+
+#ifndef BITHENGE_BLOB_H_
+#define BITHENGE_BLOB_H_
+
+#include <sys/types.h>
+
+/** A blob of raw binary data. */
+typedef struct {
+	/** @privatesection */
+	/** Operations providing random access. */
+	const struct bithenge_random_access_blob_ops_t *ops;
+} bithenge_blob_t;
+
+/** Operations providing random access to binary data.
+ * @todo Should these be thread-safe? */
+typedef struct bithenge_random_access_blob_ops_t {
+	/** @copydoc bithenge_blob_t::bithenge_blob_size */
+	int (*size)(bithenge_blob_t *blob, aoff64_t *size);
+	/** @copydoc bithenge_blob_t::bithenge_blob_read */
+	int (*read)(bithenge_blob_t *blob, aoff64_t offset, char *buffer,
+	    aoff64_t *size);
+	/** @copydoc bithenge_blob_t::bithenge_blob_destroy */
+	int (*destroy)(bithenge_blob_t *blob);
+} bithenge_random_access_blob_ops_t;
+
+/** A blob built from an object that supports only sequential reading.
+ * @implements bithenge_blob_t */
+typedef struct {
+	/** @privatesection */
+	/** The base random-access blob. */
+	bithenge_blob_t base;
+	/** Operations providing sequential access. */
+	const struct bithenge_sequential_blob_ops_t *ops;
+	/** Buffer containing all data read. */
+	char *buffer;
+	/** Size of buffer. */
+	aoff64_t buffer_size;
+	/** Amount of data actually in buffer. */
+	aoff64_t data_size;
+} bithenge_sequential_blob_t;
+
+/** Operations providing sequential access to binary data.
+ * @memberof bithenge_sequential_blob_t */
+typedef struct bithenge_sequential_blob_ops_t {
+
+	/** Get the total size of the blob. If the total size cannot be
+	 * determined easily, this field may be null or return an error,
+	 * forcing the entire blob to be read to determine its size.
+	 *
+	 * @memberof bithenge_blob_t
+	 * @param blob The blob.
+	 * @param[out] size Total size of the blob.
+	 * @return EOK on success or an error code from errno.h.
+	 */
+	int (*size)(bithenge_sequential_blob_t *blob, aoff64_t *size);
+
+	/** Read the next part of the blob. If the requested data extends
+	 * beyond the end of the blob, the data up until the end of the blob
+	 * will be read.
+	 *
+	 * @param blob The blob.
+	 * @param[out] buffer Buffer to read into. If an error occurs, the contents are
+	 * undefined.
+	 * @param[in,out] size Number of bytes to read; may be 0. If not enough
+	 * data is left in the blob, the actual number of bytes read should be
+	 * stored here. If an error occurs, the contents are undefined.
+	 * @return EOK on success or an error code from errno.h.
+	 */
+	int (*read)(bithenge_sequential_blob_t *blob, char *buffer,
+	    aoff64_t *size);
+
+	/** Destroy the blob.
+	 * @param blob The blob.
+	 * @return EOK on success or an error code from errno.h. */
+	int (*destroy)(bithenge_sequential_blob_t *blob);
+} bithenge_sequential_blob_ops_t;
+
+/** Get the total size of the blob.
+ *
+ * @memberof bithenge_blob_t
+ * @param blob The blob.
+ * @param[out] size Total size of the blob.
+ * @return EOK on success or an error code from errno.h.
+ */
+static inline int bithenge_blob_size(bithenge_blob_t *blob, aoff64_t *size)
+{
+	assert(blob);
+	assert(blob->ops);
+	return blob->ops->size(blob, size);
+}
+
+/** Read part of the blob. If the requested data extends beyond the end of the
+ * blob, the data up until the end of the blob will be read. If the offset is
+ * beyond the end of the blob, even if the size is zero, an error will be
+ * returned.
+ *
+ * @memberof bithenge_blob_t
+ * @param blob The blob.
+ * @param offset Byte offset within the blob.
+ * @param[out] buffer Buffer to read into. If an error occurs, the contents are
+ * undefined.
+ * @param[in,out] size Number of bytes to read; may be 0. If the requested
+ * range extends beyond the end of the blob, the actual number of bytes read
+ * should be stored here. If an error occurs, the contents are undefined.
+ * @return EOK on success or an error code from errno.h.
+ */
+static inline int bithenge_blob_read(bithenge_blob_t *blob, aoff64_t offset,
+    char *buffer, aoff64_t *size)
+{
+	assert(blob);
+	assert(blob->ops);
+	return blob->ops->read(blob, offset, buffer, size);
+}
+
+/** Destroy the blob.
+ * @memberof bithenge_blob_t
+ * @param blob The blob.
+ * @return EOK on success or an error code from errno.h.
+ */
+static inline int bithenge_blob_destroy(bithenge_blob_t *blob)
+{
+	assert(blob);
+	assert(blob->ops);
+	return blob->ops->destroy(blob);
+}
+
+int bithenge_new_random_access_blob(bithenge_blob_t *blob,
+    const bithenge_random_access_blob_ops_t *ops);
+
+int bithenge_new_sequential_blob(bithenge_sequential_blob_t *blob,
+    const bithenge_sequential_blob_ops_t *ops);
+
+int bithenge_new_blob_from_data(bithenge_blob_t **out, const void *data,
+    size_t len);
+
+int bithenge_new_blob_from_buffer(bithenge_blob_t **out, const void *buffer,
+    size_t len, bool needs_free);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/bithenge/block.c
===================================================================
--- uspace/app/bithenge/block.c	(revision 4b164223422e3305e9d3dafd0a78d8aca4c766f3)
+++ uspace/app/bithenge/block.c	(revision 4b164223422e3305e9d3dafd0a78d8aca4c766f3)
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2012 Sean Bartell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup bithenge
+ * @{
+ */
+/**
+ * @file
+ * Access block devices as blobs.
+ * @todo Provide more information about the block device (block size).
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <libblock.h>
+#include <loc.h>
+#include <macros.h>
+#include <stdlib.h>
+#include "blob.h"
+#include "block.h"
+
+typedef struct {
+	bithenge_blob_t base;
+	service_id_t service_id;
+	aoff64_t size;
+} block_blob_t;
+
+static inline block_blob_t *block_from_blob(bithenge_blob_t *base)
+{
+	return (block_blob_t *)base;
+}
+
+static inline bithenge_blob_t *blob_from_block(block_blob_t *blob)
+{
+	return &blob->base;
+}
+
+static int block_size(bithenge_blob_t *base, aoff64_t *size)
+{
+	block_blob_t *blob = block_from_blob(base);
+	*size = blob->size;
+	return EOK;
+}
+
+static int block_read(bithenge_blob_t *base, aoff64_t offset, char *buffer,
+    aoff64_t *size)
+{
+	block_blob_t *blob = block_from_blob(base);
+	if (offset > blob->size)
+		return ELIMIT;
+	*size = min(*size, blob->size - offset);
+	return block_read_bytes_direct(blob->service_id, offset, *size, buffer);
+}
+
+static int block_destroy(bithenge_blob_t *base)
+{
+	block_blob_t *blob = block_from_blob(base);
+	block_fini(blob->service_id);
+	free(blob);
+	return EOK;
+}
+
+static const bithenge_random_access_blob_ops_t block_ops = {
+	.size = block_size,
+	.read = block_read,
+	.destroy = block_destroy,
+};
+
+/** Create a blob for a block device. The blob must be freed with
+ * @a bithenge_blob_t::bithenge_blob_destroy after it is used.
+ * @param[out] out Stores the created blob.
+ * @param service_id The service ID of the block device.
+ * @return EOK on success or an error code from errno.h. */
+int bithenge_new_block_blob(bithenge_blob_t **out, service_id_t service_id)
+{
+	assert(out);
+
+	// Initialize libblock
+	int rc;
+	rc = block_init(EXCHANGE_SERIALIZE, service_id, 2048);
+	if (rc != EOK)
+		return rc;
+
+	// Calculate total device size
+	size_t bsize;
+	aoff64_t nblocks;
+	aoff64_t size;
+	rc = block_get_bsize(service_id, &bsize);
+	if (rc != EOK)
+		return rc;
+	rc = block_get_nblocks(service_id, &nblocks);
+	if (rc != EOK)
+		return rc;
+	size = bsize * nblocks;
+
+	// Create blob
+	block_blob_t *blob = malloc(sizeof(*blob));
+	if (!blob)
+		return ENOMEM;
+	rc = bithenge_new_random_access_blob(blob_from_block(blob),
+	    &block_ops);
+	if (rc != EOK) {
+		free(blob);
+		return rc;
+	}
+	blob->service_id = service_id;
+	blob->size = size;
+	*out = blob_from_block(blob);
+
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/app/bithenge/block.h
===================================================================
--- uspace/app/bithenge/block.h	(revision 4b164223422e3305e9d3dafd0a78d8aca4c766f3)
+++ uspace/app/bithenge/block.h	(revision 4b164223422e3305e9d3dafd0a78d8aca4c766f3)
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2012 Sean Bartell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup bithenge
+ * @{
+ */
+/**
+ * @file
+ * Access block devices as blobs.
+ */
+
+#ifndef BITHENGE_BLOCK_H_
+#define BITHENGE_BLOCK_H_
+
+#include <loc.h>
+#include "blob.h"
+
+int bithenge_new_block_blob(bithenge_blob_t **, service_id_t);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/bithenge/test.c
===================================================================
--- uspace/app/bithenge/test.c	(revision 4b164223422e3305e9d3dafd0a78d8aca4c766f3)
+++ uspace/app/bithenge/test.c	(revision 4b164223422e3305e9d3dafd0a78d8aca4c766f3)
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2012 Sean Bartell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup bithenge
+ * @{
+ */
+/**
+ * @file
+ * Simple program to test Bithenge.
+ */
+
+#include <loc.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include "blob.h"
+#include "block.h"
+
+static void
+print_data(const char *data, size_t len)
+{
+	while (len--)
+		printf("%02x ", (uint8_t)(*data++));
+	printf("\n");
+}
+
+static void
+print_blob(bithenge_blob_t *blob)
+{
+	aoff64_t size;
+	bithenge_blob_size(blob, &size);
+	printf("Size: %d; ", (int)size);
+	char buffer[64];
+	size = sizeof(buffer);
+	bithenge_blob_read(blob, 0, buffer, &size);
+	print_data(buffer, size);
+}
+
+int main(int argc, char *argv[])
+{
+	bithenge_blob_t *blob;
+
+	service_id_t service_id;
+	loc_service_get_id("bd/initrd", &service_id, 0);
+	bithenge_new_block_blob(&blob, service_id);
+	printf("Data from block:bd/initrd: ");
+	print_blob(blob);
+	bithenge_blob_destroy(blob);
+
+	const char data[] = "'Twas brillig, and the slithy toves";
+	bithenge_new_blob_from_data(&blob, data, sizeof(data));
+	printf("Data from memory (from_data): ");
+	print_blob(blob);
+	bithenge_blob_destroy(blob);
+
+	bithenge_new_blob_from_buffer(&blob, data, sizeof(data), false);
+	printf("Data from memory (from_buffer): ");
+	print_blob(blob);
+	bithenge_blob_destroy(blob);
+
+	return 0;
+}
+
+/** @}
+ */
