Index: uspace/app/bithenge/expression.c
===================================================================
--- uspace/app/bithenge/expression.c	(revision 0153c87e5c9c1080a884969b29e7aa3388bd5a64)
+++ uspace/app/bithenge/expression.c	(revision 6be4142b13aac4b9ed8b2805a5a6998491afc977)
@@ -464,4 +464,6 @@
 	rc = bithenge_node_get(node, self->key, out);
 	bithenge_node_dec_ref(node);
+	if (rc == ENOENT)
+		return bithenge_scope_error(scope, "No member %t", self->key);
 	return rc;
 }
@@ -549,5 +551,5 @@
 			return rc;
 	}
-	return ENOENT;
+	return bithenge_scope_error(scope, "No scope member %t", self->key);
 }
 
Index: uspace/app/bithenge/print.c
===================================================================
--- uspace/app/bithenge/print.c	(revision 0153c87e5c9c1080a884969b29e7aa3388bd5a64)
+++ uspace/app/bithenge/print.c	(revision 6be4142b13aac4b9ed8b2805a5a6998491afc977)
@@ -38,4 +38,5 @@
 
 #include <errno.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include "blob.h"
@@ -47,13 +48,34 @@
 	bool first;
 	int depth;
+	char *buffer;
+	size_t buffer_size;
 } state_t;
 
+static void state_printf(state_t *state, const char *format, ...)
+{
+	va_list ap;
+	va_start(ap, format);
+	if (state->buffer) {
+		int rc = vsnprintf(state->buffer, state->buffer_size, format,
+		    ap);
+		if (rc > 0 && (size_t)rc >= state->buffer_size)
+			rc = state->buffer_size - 1;
+		if (rc > 0) {
+			state->buffer += rc;
+			state->buffer_size -= rc;
+		}
+	} else {
+		vprintf(format, ap);
+	}
+	va_end(ap);
+}
+
 static int print_node(state_t *, bithenge_node_t *);
 
 static void newline(state_t *state)
 {
-	printf("\n");
+	state_printf(state, "\n");
 	for (int i = 0; i < state->depth; i++) {
-		printf("    ");
+		state_printf(state, "    ");
 	}
 }
@@ -74,5 +96,5 @@
 	int rc = EOK;
 	if (!state->first)
-		printf(",");
+		state_printf(state, ",");
 	newline(state);
 	state->first = false;
@@ -80,11 +102,11 @@
 	    && bithenge_node_type(key) != BITHENGE_NODE_STRING;
 	if (add_quotes)
-		printf("\"");
+		state_printf(state, "\"");
 	rc = print_node(state, key);
 	if (rc != EOK)
 		goto end;
 	if (add_quotes)
-		printf("\"");
-	printf(": ");
+		state_printf(state, "\"");
+	state_printf(state, ": ");
 	rc = print_node(state, value);
 	if (rc != EOK)
@@ -99,5 +121,5 @@
 {
 	int rc;
-	printf("{");
+	state_printf(state, "{");
 	increase_depth(state);
 	state->first = true;
@@ -109,5 +131,5 @@
 		newline(state);
 	state->first = false;
-	printf("}");
+	state_printf(state, "}");
 	return EOK;
 }
@@ -118,8 +140,8 @@
 	switch (state->type) {
 	case BITHENGE_PRINT_PYTHON:
-		printf(value ? "True" : "False");
+		state_printf(state, value ? "True" : "False");
 		break;
 	case BITHENGE_PRINT_JSON:
-		printf(value ? "true" : "false");
+		state_printf(state, value ? "true" : "false");
 		break;
 	}
@@ -130,5 +152,5 @@
 {
 	bithenge_int_t value = bithenge_integer_node_value(node);
-	printf("%" BITHENGE_PRId, value);
+	state_printf(state, "%" BITHENGE_PRId, value);
 	return EOK;
 }
@@ -137,5 +159,5 @@
 {
 	const char *value = bithenge_string_node_value(node);
-	printf("\"");
+	state_printf(state, "\"");
 	for (string_iterator_t i = string_iterator(value); !string_iterator_done(&i); ) {
 		wchar_t ch;
@@ -144,12 +166,12 @@
 			return rc;
 		if (ch == '"' || ch == '\\') {
-			printf("\\%lc", (wint_t) ch);
+			state_printf(state, "\\%lc", (wint_t) ch);
 		} else if (ch <= 0x1f) {
-			printf("\\u%04x", (unsigned int) ch);
+			state_printf(state, "\\u%04x", (unsigned int) ch);
 		} else {
-			printf("%lc", (wint_t) ch);
+			state_printf(state, "%lc", (wint_t) ch);
 		}
 	}
-	printf("\"");
+	state_printf(state, "\"");
 	return EOK;
 }
@@ -162,5 +184,6 @@
 	aoff64_t size = sizeof(buffer);
 	int rc;
-	printf(state->type == BITHENGE_PRINT_PYTHON ? "b\"" : "\"");
+	state_printf(state,
+	    state->type == BITHENGE_PRINT_PYTHON ? "b\"" : "\"");
 	do {
 		rc = bithenge_blob_read(blob, pos, buffer, &size);
@@ -168,8 +191,9 @@
 			return rc;
 		for (aoff64_t i = 0; i < size; i++)
-			printf("\\x%02x", (unsigned int)(uint8_t)buffer[i]);
+			state_printf(state, "\\x%02x",
+			    (unsigned int)(uint8_t)buffer[i]);
 		pos += size;
 	} while (size == sizeof(buffer));
-	printf("\"");
+	state_printf(state, "\"");
 	return EOK;
 }
@@ -192,5 +216,5 @@
 }
 
-/** Print a tree as text.
+/** Print a tree as text to stdout.
  * @param type The format to use.
  * @param tree The root node of the tree to print.
@@ -198,8 +222,26 @@
 int bithenge_print_node(bithenge_print_type_t type, bithenge_node_t *tree)
 {
-	state_t state = {type, true, 0};
+	state_t state = {type, true, 0, NULL, 0};
 	return print_node(&state, tree);
 }
 
+/** Print a tree as text into a buffer.
+ * @param[in,out] str Holds a pointer to the buffer; changed to point to the
+ * null character.
+ * @param[in,out] size Holds the size of the buffer; changed to hold the
+ * remaining size.
+ * @param type The format to use.
+ * @param tree The root node of the tree to print.
+ * @return EOK on success or an error code from errno.h. */
+int bithenge_print_node_to_string(char **str, size_t *size,
+    bithenge_print_type_t type, bithenge_node_t *tree)
+{
+	state_t state = {type, true, 0, *str, *size};
+	int rc = print_node(&state, tree);
+	*str = state.buffer;
+	*size = state.buffer_size;
+	return rc;
+}
+
 /** @}
  */
Index: uspace/app/bithenge/print.h
===================================================================
--- uspace/app/bithenge/print.h	(revision 0153c87e5c9c1080a884969b29e7aa3388bd5a64)
+++ uspace/app/bithenge/print.h	(revision 6be4142b13aac4b9ed8b2805a5a6998491afc977)
@@ -51,4 +51,6 @@
 
 int bithenge_print_node(bithenge_print_type_t, bithenge_node_t *);
+int bithenge_print_node_to_string(char **, size_t *, bithenge_print_type_t,
+    bithenge_node_t *);
 
 #endif
Index: uspace/app/bithenge/sequence.c
===================================================================
--- uspace/app/bithenge/sequence.c	(revision 0153c87e5c9c1080a884969b29e7aa3388bd5a64)
+++ uspace/app/bithenge/sequence.c	(revision 6be4142b13aac4b9ed8b2805a5a6998491afc977)
@@ -418,8 +418,30 @@
 	struct_node_t *node = node_as_struct(base);
 
-	/* We didn't inc_ref for the scope in struct_transform_make_node, so
-	 * make sure it doesn't try to dec_ref. */
-	seq_node_scope(struct_as_seq(node))->current_node = NULL;
-	seq_node_destroy(struct_as_seq(node));
+	/* Treat the scope carefully because of the circular reference. In
+	 * struct_transform_make_node, things are set up so node owns a
+	 * reference to the scope, but scope doesn't own a reference to node,
+	 * so node's reference count is too low. */
+	bithenge_scope_t *scope = seq_node_scope(struct_as_seq(node));
+	if (scope->refs == 1) {
+		/* Mostly normal destroy, but we didn't inc_ref(node) for the
+		 * scope in struct_transform_make_node, so make sure it doesn't
+		 * try to dec_ref. */
+		scope->current_node = NULL;
+		seq_node_destroy(struct_as_seq(node));
+	} else if (scope->refs > 1) {
+		/* The scope is still needed, but node isn't otherwise needed.
+		 * Switch things around so scope owns a reference to node, but
+		 * not vice versa, and scope's reference count is too low. */
+		bithenge_node_inc_ref(base);
+		bithenge_scope_dec_ref(scope);
+		return;
+	} else {
+		/* This happens after the previous case, when scope is no
+		 * longer used and is being destroyed. Since scope is already
+		 * being destroyed, set it to NULL here so we don't try to
+		 * destroy it twice. */
+		struct_as_seq(node)->scope = NULL;
+		seq_node_destroy(struct_as_seq(node));
+	}
 
 	bithenge_transform_dec_ref(struct_as_transform(node->transform));
Index: uspace/app/bithenge/test.c
===================================================================
--- uspace/app/bithenge/test.c	(revision 0153c87e5c9c1080a884969b29e7aa3388bd5a64)
+++ uspace/app/bithenge/test.c	(revision 6be4142b13aac4b9ed8b2805a5a6998491afc977)
@@ -50,22 +50,6 @@
 	int rc;
 	if (argc < 3) {
-		// {True: {}, -1351: "\"false\"", "true": False, 0: b"..."}
-		const char data[] = "'Twas brillig, and the slithy toves";
-		bithenge_node_t *node;
-		bithenge_node_t *subnodes[8];
-		bithenge_new_boolean_node(&subnodes[0], true);
-		bithenge_new_simple_internal_node(&subnodes[1], NULL, 0, false);
-		bithenge_new_integer_node(&subnodes[2], -1351);
-		bithenge_new_string_node(&subnodes[3], "\"false\"", false);
-		bithenge_new_string_node(&subnodes[4], "true", false);
-		bithenge_new_boolean_node(&subnodes[5], false);
-		bithenge_new_integer_node(&subnodes[6], 0);
-		bithenge_new_blob_from_data(&subnodes[7], data, sizeof(data));
-		bithenge_new_simple_internal_node(&node, subnodes, 4, false);
-		bithenge_print_node(BITHENGE_PRINT_PYTHON, node);
-		printf("\n");
-		bithenge_print_node(BITHENGE_PRINT_JSON, node);
-		printf("\n");
-		bithenge_node_dec_ref(node);
+		fprintf(stderr, "Usage: %s <script> <source>\n", argv[0]);
+		return 1;
 	} else {
 		bithenge_scope_t *scope = NULL;
@@ -96,5 +80,7 @@
 		rc = bithenge_transform_apply(transform, scope, node, &node2);
 		if (rc != EOK) {
-			printf("Error applying transform: %s\n", str_error(rc));
+			const char *message = bithenge_scope_get_error(scope);
+			printf("Error applying transform: %s\n",
+			    message ? message : str_error(rc));
 			node2 = NULL;
 			goto error;
@@ -108,5 +94,7 @@
 		rc = bithenge_print_node(BITHENGE_PRINT_PYTHON, node2);
 		if (rc != EOK) {
-			printf("Error printing node: %s\n", str_error(rc));
+			const char *message = bithenge_scope_get_error(scope);
+			printf("Error printing node: %s\n",
+			    message ? message : str_error(rc));
 			goto error;
 		}
Index: uspace/app/bithenge/transform.c
===================================================================
--- uspace/app/bithenge/transform.c	(revision 0153c87e5c9c1080a884969b29e7aa3388bd5a64)
+++ uspace/app/bithenge/transform.c	(revision 6be4142b13aac4b9ed8b2805a5a6998491afc977)
@@ -37,6 +37,8 @@
 #include <assert.h>
 #include <errno.h>
+#include <stdarg.h>
 #include <stdlib.h>
 #include "blob.h"
+#include "print.h"
 #include "transform.h"
 
@@ -187,4 +189,5 @@
 		bithenge_scope_inc_ref(outer);
 	self->outer = outer;
+	self->error = NULL;
 	self->barrier = false;
 	self->num_params = 0;
@@ -209,4 +212,5 @@
 	bithenge_scope_dec_ref(self->outer);
 	free(self->params);
+	free(self->error);
 	free(self);
 }
@@ -218,4 +222,64 @@
 {
 	return self->outer;
+}
+
+/** Get the error message stored in the scope, which may be NULL. The error
+ * message only exists as long as the scope does.
+ * @param scope The scope to get the error message from.
+ * @return The error message, or NULL. */
+const char *bithenge_scope_get_error(bithenge_scope_t *scope)
+{
+	return scope->error;
+}
+
+/** Set the error message for the scope. The error message is stored in the
+ * outermost scope, but if any scope already has an error message this error
+ * message is ignored.
+ * @param scope The scope.
+ * @param format The format string.
+ * @return EINVAL normally, or another error code from errno.h. */
+int bithenge_scope_error(bithenge_scope_t *scope, const char *format, ...)
+{
+	if (scope->error)
+		return EINVAL;
+	while (scope->outer) {
+		scope = scope->outer;
+		if (scope->error)
+			return EINVAL;
+	}
+	size_t space_left = 256;
+	scope->error = malloc(space_left);
+	if (!scope->error)
+		return ENOMEM;
+	char *out = scope->error;
+	va_list ap;
+	va_start(ap, format);
+
+	while (*format) {
+		if (format[0] == '%' && format[1] == 't') {
+			format += 2;
+			int rc = bithenge_print_node_to_string(&out,
+			    &space_left, BITHENGE_PRINT_PYTHON,
+			    va_arg(ap, bithenge_node_t *));
+			if (rc != EOK) {
+				va_end(ap);
+				return rc;
+			}
+		} else {
+			const char *end = str_chr(format, '%');
+			if (!end)
+				end = format + str_length(format);
+			size_t size = min((size_t)(end - format),
+			    space_left - 1);
+			memcpy(out, format, size);
+			format = end;
+			out += size;
+			space_left -= size;
+		}
+	}
+	*out = '\0';
+
+	va_end(ap);
+	return EINVAL;
 }
 
Index: uspace/app/bithenge/transform.h
===================================================================
--- uspace/app/bithenge/transform.h	(revision 0153c87e5c9c1080a884969b29e7aa3388bd5a64)
+++ uspace/app/bithenge/transform.h	(revision 6be4142b13aac4b9ed8b2805a5a6998491afc977)
@@ -54,4 +54,5 @@
 	unsigned int refs;
 	struct bithenge_scope *outer;
+	char *error;
 	bool barrier;
 	int num_params;
@@ -152,4 +153,6 @@
 void bithenge_scope_dec_ref(bithenge_scope_t *);
 bithenge_scope_t *bithenge_scope_outer(bithenge_scope_t *);
+const char *bithenge_scope_get_error(bithenge_scope_t *);
+int bithenge_scope_error(bithenge_scope_t *, const char *, ...);
 bithenge_node_t *bithenge_scope_get_current_node(bithenge_scope_t *);
 void bithenge_scope_set_current_node(bithenge_scope_t *, bithenge_node_t *);
Index: uspace/dist/src/bithenge/fat.bh
===================================================================
--- uspace/dist/src/bithenge/fat.bh	(revision 0153c87e5c9c1080a884969b29e7aa3388bd5a64)
+++ uspace/dist/src/bithenge/fat.bh	(revision 6be4142b13aac4b9ed8b2805a5a6998491afc977)
@@ -27,4 +27,5 @@
 # FAT filesystem script.
 # Largely based on https://en.wikipedia.org/wiki/File_Allocation_Table
+# Currently only FAT12 and FAT16 are supported.
 
 transform u8 = uint8;
@@ -32,8 +33,19 @@
 transform u32 = uint32le;
 
+transform fat_attributes = struct {
+	.read_only <- bit;
+	.hidden <- bit;
+	.system <- bit;
+	.volume_label <- bit;
+	.subdirectory <- bit;
+	.archive <- bit;
+	.device <- bit;
+	.reserved <- bit;
+} <- bits_le <- known_length(1);
+
 transform fat_dir_entry(disk) = struct {
 	.filename <- known_length(8);
 	.extension <- known_length(3);
-	.attrs <- u8;
+	.attrs <- fat_attributes;
 	.flags <- u8;
 	.ctime_fine <- u8;
@@ -79,32 +91,45 @@
 	};
 
-	.num_sectors <- if (.bpb331.ignore) {
-		(.num_sectors_16)
-	} else {
-		(.bpb331.num_sectors_32)
-	};
-
-	.first_root_sector <- (.num_reserved_sectors + .num_fats * .sectors_per_fat);
-	.first_data_sector <- (.first_root_sector +
-	    (.num_root_entries * 32 + .bytes_per_sector - 1) //
-	    .bytes_per_sector);
-	.num_clusters <- (2 + (.num_sectors - .first_data_sector) // .sectors_per_cluster);
-	.bits <- if (.num_clusters < 4085) { (12) }
-	    else { if (.num_clusters < 65525) { (16) } else { (32) } };
-
-	.fats <- partial(.num_reserved_sectors * .bytes_per_sector) {
-		repeat(.num_fats) {
-			fat_table(.bits, .num_clusters) <-
-				known_length(.sectors_per_fat * .bytes_per_sector)
-		}
-	} <- (disk);
-
-	.root <- partial(.first_root_sector * .bytes_per_sector) {
-		repeat(.num_root_entries) { fat_dir_entry(disk) } } <- (disk);
+	.drive_number <- u8;
+	.chkdsk_flags <- u8;
+	.extended_boot_signature <- u8;
+	if (.extended_boot_signature == 41) {
+		.volume_id <- u32;
+		.volume_label <- ascii <- known_length(11);
+		.type <- ascii <- known_length(8);
+	}
 
 	.boot_signature <- (disk[510,2]); # b"\x55\xaa"; TODO: what if .bytes_per_sector < 512?
 };
 
-transform fat_filesystem = partial { fat_super(in) };
+transform fat_filesystem_tree(disk) = struct {
+	.super <- partial{fat_super(disk)} <- (disk);
+
+	.num_sectors <- if (.super.bpb331.ignore) {
+		(.super.num_sectors_16)
+	} else {
+		(.super.bpb331.num_sectors_32)
+	};
+
+	.first_root_sector <- (.super.num_reserved_sectors + .super.num_fats * .super.sectors_per_fat);
+	.first_data_sector <- (.first_root_sector +
+	    (.super.num_root_entries * 32 + .super.bytes_per_sector - 1) //
+	    .super.bytes_per_sector);
+	.num_clusters <- (2 + (.num_sectors - .first_data_sector) // .super.sectors_per_cluster);
+	.bits <- if (.num_clusters < 4085) { (12) }
+	    else { if (.num_clusters < 65525) { (16) } else { (32) } };
+
+	.fats <- partial(.super.num_reserved_sectors * .super.bytes_per_sector) {
+		repeat(.super.num_fats) {
+			fat_table(.bits, .num_clusters) <-
+				known_length(.super.sectors_per_fat * .super.bytes_per_sector)
+		}
+	} <- (disk);
+
+	.root <- partial(.first_root_sector * .super.bytes_per_sector) {
+		repeat(.super.num_root_entries) { fat_dir_entry(disk) } } <- (disk);
+};
+
+transform fat_filesystem = partial {fat_filesystem_tree(in)};
 
 transform main = fat_filesystem;
