static bool _save_block_index_record
(struct sieve_binary *sbin, struct ostream *stream, unsigned int id)
{
	struct sieve_binary_block *block;
	struct sieve_binary_block_index header;

	block = sieve_binary_block_get(sbin, id);
	if ( block == NULL )
		return FALSE;

	header.id = id;
	header.size = buffer_get_used_size(block->data);
	header.ext_id = block->ext_index;
	header.offset = block->offset;

	if ( !_save_full(sbin, stream, &header, sizeof(header)) ) {
		sieve_sys_error(sbin->svinst,
			"binary save: failed to save block index header %d", id);
		return FALSE;
	}

	return TRUE;
}
static bool _save_block
(struct sieve_binary *sbin, struct ostream *stream, unsigned int id)
{
	struct sieve_binary_block_header block_header;
	struct sieve_binary_block *block;
	const void *data;
	size_t size;

	block = sieve_binary_block_get(sbin, id);
	if ( block == NULL )
		return FALSE;

	data = buffer_get_data(block->data, &size);

	block_header.id = id;
	block_header.size = size;

	if ( !_save_aligned(sbin, stream, &block_header,
		sizeof(block_header), &block->offset) )
		return FALSE;

	return _save_aligned(sbin, stream, data, size, NULL);
}
struct sieve_binary *sieve_generator_run
(struct sieve_generator *gentr, struct sieve_binary_block **sblock_r)
{
	bool topmost = ( sblock_r == NULL || *sblock_r == NULL );
	struct sieve_binary *sbin;
	struct sieve_binary_block *sblock, *debug_block;
	const struct sieve_extension *const *extensions;
	unsigned int i, ext_count;
	bool result = TRUE;

	/* Initialize */

	if ( topmost ) {
		sbin = sieve_binary_create_new(sieve_ast_script(gentr->genenv.ast));
		sblock = sieve_binary_block_get(sbin, SBIN_SYSBLOCK_MAIN_PROGRAM);
	} else {
		sblock = *sblock_r;
		sbin = sieve_binary_block_get_binary(sblock);
	}

	i_assert(sbin != NULL);

	sieve_binary_ref(sbin);
	gentr->genenv.sbin = sbin;
	gentr->genenv.sblock = sblock;

	/* Create debug block */
	debug_block = sieve_binary_block_create(sbin);
	gentr->dwriter = sieve_binary_debug_writer_init(debug_block);
	(void)sieve_binary_emit_unsigned
		(sblock, sieve_binary_block_get_id(debug_block));

	/* Load extensions linked to the AST and emit a list in code */
	extensions = sieve_ast_extensions_get(gentr->genenv.ast, &ext_count);
	(void) sieve_binary_emit_unsigned(sblock, ext_count);
	for ( i = 0; i < ext_count; i++ ) {
		const struct sieve_extension *ext = extensions[i];

		/* Link to binary */
		(void)sieve_binary_extension_link(sbin, ext);

		/* Emit */
		sieve_binary_emit_extension(sblock, ext, 0);

		/* Load */
		if ( ext->def != NULL && ext->def->generator_load != NULL &&
			!ext->def->generator_load(ext, &gentr->genenv) )
			result = FALSE;
	}

	/* Generate code */

	if ( result ) {
		if ( !sieve_generate_block
			(&gentr->genenv, sieve_ast_root(gentr->genenv.ast)))
			result = FALSE;
		else if ( topmost )
			sieve_binary_activate(sbin);
	}

	/* Cleanup */

	gentr->genenv.sbin = NULL;
	gentr->genenv.sblock = NULL;
	sieve_binary_unref(&sbin);

	if ( !result ) {
		if ( topmost ) {
			sieve_binary_unref(&sbin);
			if ( sblock_r != NULL )
				*sblock_r = NULL;
		}
		sbin = NULL;
	} else {
		if ( sblock_r != NULL )
			*sblock_r = sblock;
	}

	return sbin;
}
static bool _sieve_binary_open(struct sieve_binary *sbin)
{
	bool result = TRUE;
	off_t offset = 0;
	const struct sieve_binary_header *header;
	struct sieve_binary_block *ext_block;
	unsigned int i, blk_count;
	int ret;

	/* Verify header */

	T_BEGIN {
		header = LOAD_HEADER(sbin, &offset, const struct sieve_binary_header);
		/* Check header presence */
		if ( header == NULL ) {
			sieve_sys_error(sbin->svinst,
				"binary_open: file %s is not large enough to contain the header.",
				sbin->path);
			result = FALSE;

		/* Check header validity */
		} else if ( header->magic != SIEVE_BINARY_MAGIC ) {
			if ( header->magic != SIEVE_BINARY_MAGIC_OTHER_ENDIAN )
				sieve_sys_error(sbin->svinst,
					"binary_open: binary %s has corrupted header "
					"(0x%08x) or it is not a Sieve binary", sbin->path, header->magic);
			else if ( sbin->svinst->debug )
				sieve_sys_debug(sbin->svinst,
					"binary open: binary %s stored with in different endian format "
					"(automatically fixed when re-compiled)",
					sbin->path);
			result = FALSE;

		/* Check binary version */
		} else if ( result && (
		  header->version_major != SIEVE_BINARY_VERSION_MAJOR ||
			header->version_minor != SIEVE_BINARY_VERSION_MINOR ) ) {

			/* Binary is of different version. Caller will have to recompile */

			if ( sbin->svinst->debug ) {
				sieve_sys_debug(sbin->svinst,
					"binary open: binary %s stored with different binary version %d.%d "
					"(!= %d.%d; automatically fixed when re-compiled)", sbin->path,
					(int) header->version_major, header->version_minor,
					SIEVE_BINARY_VERSION_MAJOR, SIEVE_BINARY_VERSION_MINOR);
			}
			result = FALSE;

		/* Check block content */
		} else if ( result && header->blocks == 0 ) {
			sieve_sys_error(sbin->svinst,
				"binary open: binary %s is corrupt: it contains no blocks",
				sbin->path);
			result = FALSE;

		/* Valid */
		} else {
			blk_count = header->blocks;
		}
	} T_END;

	if ( !result ) return FALSE;

	/* Load block index */

	for ( i = 0; i < blk_count && result; i++ ) {
		T_BEGIN {
			if ( !_read_block_index_record(sbin, &offset, i) ) {
				result = FALSE;
			}
		} T_END;
	}

	if ( !result ) return FALSE;

	/* Load extensions used by this binary */

	T_BEGIN {
		ext_block = sieve_binary_block_get(sbin, SBIN_SYSBLOCK_EXTENSIONS);
		if ( ext_block == NULL ) {
			result = FALSE;
		} else if ( (ret=_read_extensions(ext_block)) <= 0 ) {
			if ( ret < 0 ) {
				sieve_sys_error(sbin->svinst,
					"binary open: binary %s is corrupt: failed to load extension block",
					sbin->path);
			}
			result = FALSE;
		}
	} T_END;

	return result;
}
static bool _sieve_binary_save
(struct sieve_binary *sbin, struct ostream *stream)
{
	struct sieve_binary_header header;
	struct sieve_binary_extension_reg *const *regs;
	struct sieve_binary_block *ext_block;
	unsigned int ext_count, blk_count, i;
	uoff_t block_index;

	blk_count = sieve_binary_block_count(sbin);

	/* Signal all extensions to finish generating their blocks */

	regs = array_get(&sbin->extensions, &ext_count);
	for ( i = 0; i < ext_count; i++ ) {
		const struct sieve_binary_extension *binext = regs[i]->binext;

		if ( binext != NULL && binext->binary_save != NULL )
			binext->binary_save(regs[i]->extension, sbin, regs[i]->context);
	}

	/* Create header */

	header.magic = SIEVE_BINARY_MAGIC;
	header.version_major = SIEVE_BINARY_VERSION_MAJOR;
	header.version_minor = SIEVE_BINARY_VERSION_MINOR;
	header.blocks = blk_count;

	if ( !_save_aligned(sbin, stream, &header, sizeof(header), NULL) ) {
		sieve_sys_error(sbin->svinst, "binary save: failed to save header");
		return FALSE;
	}

	/* Skip block index for now */

	if ( !_save_skip_aligned(sbin, stream,
		sizeof(struct sieve_binary_block_index) * blk_count, &block_index) )
		return FALSE;

	/* Create block containing all used extensions */

	ext_block = sieve_binary_block_get(sbin, SBIN_SYSBLOCK_EXTENSIONS);
	i_assert( ext_block != NULL );
	sieve_binary_block_clear(ext_block);

	ext_count = array_count(&sbin->linked_extensions);
	sieve_binary_emit_unsigned(ext_block, ext_count);

	for ( i = 0; i < ext_count; i++ ) {
		struct sieve_binary_extension_reg * const *ext
			= array_idx(&sbin->linked_extensions, i);

		sieve_binary_emit_cstring
			(ext_block, sieve_extension_name((*ext)->extension));
		sieve_binary_emit_unsigned
			(ext_block, sieve_extension_version((*ext)->extension));
		sieve_binary_emit_unsigned(ext_block, (*ext)->block_id);
	}

	/* Save all blocks into the binary */

	for ( i = 0; i < blk_count; i++ ) {
		if ( !_save_block(sbin, stream, i) )
			return FALSE;
	}

	/* Create the block index */
	o_stream_seek(stream, block_index);
	for ( i = 0; i < blk_count; i++ ) {
		if ( !_save_block_index_record(sbin, stream, i) )
			return FALSE;
	}

	return TRUE;
}