bool sieve_binary_load_block (struct sieve_binary_block *sblock) { struct sieve_binary *sbin = sblock->sbin; unsigned int id = sblock->id; off_t offset = sblock->offset; const struct sieve_binary_block_header *header = LOAD_HEADER(sbin, &offset, const struct sieve_binary_block_header); if ( header == NULL ) { sieve_sys_error(sbin->svinst, "binary load: binary %s is corrupt: " "failed to read header of block %d", sbin->path, id); return FALSE; } if ( header->id != id ) { sieve_sys_error(sbin->svinst, "binary load: binary %s is corrupt: " "header of block %d has non-matching id %d", sbin->path, id, header->id); return FALSE; } sblock->data = sbin->file->load_buffer(sbin->file, &offset, header->size); if ( sblock->data == NULL ) { sieve_sys_error(sbin->svinst, "binary load: failed to read block %d of binary %s (size=%d)", id, sbin->path, header->size); return FALSE; } return TRUE; }
static bool _read_block_index_record (struct sieve_binary *sbin, off_t *offset, unsigned int id) { const struct sieve_binary_block_index *record = LOAD_HEADER(sbin, offset, const struct sieve_binary_block_index); struct sieve_binary_block *block; if ( record == NULL ) { sieve_sys_error(sbin->svinst, "binary open: binary %s is corrupt: " "failed to load block index record %d", sbin->path, id); return FALSE; } if ( record->id != id ) { sieve_sys_error(sbin->svinst, "binary open: binary %s is corrupt: " "block index record %d has unexpected id %d", sbin->path, id, record->id); return FALSE; } block = sieve_binary_block_create_id(sbin, id); block->ext_index = record->ext_id; block->offset = record->offset; return TRUE; }
static bool _file_lazy_read (struct sieve_binary_file *file, off_t *offset, void *buffer, size_t size) { struct sieve_instance *svinst = file->svinst; int ret; void *indata = buffer; size_t insize = size; *offset = SIEVE_BINARY_ALIGN(*offset); /* Seek to the correct position */ if ( *offset != file->offset && lseek(file->fd, *offset, SEEK_SET) == (off_t) -1 ) { sieve_sys_error(svinst, "binary read:" "failed to seek(fd, %lld, SEEK_SET) in binary %s: %m", (long long) *offset, file->path); return FALSE; } /* Read record into memory */ while (insize > 0) { if ( (ret=read(file->fd, indata, insize)) <= 0 ) { if ( ret == 0 ) sieve_sys_error(svinst, "binary read: binary %s is truncated (more data expected)", file->path); else sieve_sys_error(svinst, "binary read: failed to read from binary %s: %m", file->path); break; } indata = PTR_OFFSET(indata, ret); insize -= ret; } if ( insize != 0 ) { /* Failed to read the whole requested record */ return FALSE; } *offset += size; file->offset = *offset; return TRUE; }
static bool ext_ihave_binary_open (const struct sieve_extension *ext, struct sieve_binary *sbin, void *context) { struct sieve_instance *svinst = ext->svinst; struct ext_ihave_binary_context *binctx = (struct ext_ihave_binary_context *) context; struct sieve_binary_block *sblock; unsigned int i, count, block_id; sieve_size_t offset; sblock = sieve_binary_extension_get_block(sbin, ext); if ( sblock != NULL ) { binctx->block = sblock; block_id = sieve_binary_block_get_id(sblock); offset = 0; /* Read number of missing extensions to read subsequently */ if ( !sieve_binary_read_unsigned(sblock, &offset, &count) ) { sieve_sys_error(svinst, "ihave: failed to read missing extension count " "from block %d of binary %s", block_id, sieve_binary_path(sbin)); return FALSE; } /* Read dependencies */ for ( i = 0; i < count; i++ ) { string_t *ext_name; const char *name; if ( !sieve_binary_read_string(sblock, &offset, &ext_name) ) { /* Binary is corrupt, recompile */ sieve_sys_error(svinst, "ihave: failed to read missing extension name " "from block %d of binary %s", block_id, sieve_binary_path(sbin)); return FALSE; } name = str_c(ext_name); array_append(&binctx->missing_extensions, &name, 1); } } return TRUE; }
static bool _file_memory_load(struct sieve_binary_file *file) { struct _file_memory *fmem = (struct _file_memory *) file; int ret; size_t size; void *indata; i_assert(file->fd > 0); /* Allocate memory buffer */ indata = p_malloc(file->pool, file->st.st_size); size = file->st.st_size; file->offset = 0; fmem->memory = indata; fmem->memory_size = file->st.st_size; /* Return to beginning of the file */ if ( lseek(file->fd, 0, SEEK_SET) == (off_t) -1 ) { sieve_sys_error("failed to seek() in binary %s: %m", file->path); return FALSE; } /* Read the whole file into memory */ while (size > 0) { if ( (ret=read(file->fd, indata, size)) <= 0 ) { sieve_sys_error("failed to read from binary %s: %m", file->path); break; } indata = PTR_OFFSET(indata, ret); size -= ret; } if ( size != 0 ) { /* Failed to read the whole file */ return FALSE; } return TRUE; }
static bool _sieve_extension_load(struct sieve_extension *ext) { /* Call load handler */ if ( ext->def != NULL && ext->def->load != NULL && !ext->def->load(ext, &ext->context) ) { sieve_sys_error(ext->svinst, "failed to load '%s' extension support.", ext->def->name); return FALSE; } return TRUE; }
static inline bool _save_skip (struct sieve_binary *sbin, struct ostream *stream, size_t size) { if ( (o_stream_seek(stream, stream->offset + size)) <= 0 ) { sieve_sys_error(sbin->svinst, "binary save: failed to skip output stream " "to position %"PRIuUOFF_T": %s", stream->offset + size, strerror(stream->stream_errno)); return FALSE; } return TRUE; }
void sieve_binary_file_close(struct sieve_binary_file **file) { if ( (*file)->fd != -1 ) { if ( close((*file)->fd) < 0 ) { sieve_sys_error((*file)->svinst, "binary close: failed to close: close(fd=%s) failed: %m", (*file)->path); } } pool_unref(&(*file)->pool); *file = NULL; }
static int _read_extensions(struct sieve_binary_block *sblock) { struct sieve_binary *sbin = sblock->sbin; sieve_size_t offset = 0; unsigned int i, count; bool result = 1; if ( !sieve_binary_read_unsigned(sblock, &offset, &count) ) return -1; for ( i = 0; result > 0 && i < count; i++ ) { T_BEGIN { string_t *extension; const struct sieve_extension *ext; unsigned int version; if ( sieve_binary_read_string(sblock, &offset, &extension) ) { ext = sieve_extension_get_by_name(sbin->svinst, str_c(extension)); if ( ext == NULL ) { sieve_sys_error(sbin->svinst, "binary open: binary %s requires unknown extension `%s'", sbin->path, str_sanitize(str_c(extension), 128)); result = 0; } else { struct sieve_binary_extension_reg *ereg = NULL; (void) sieve_binary_extension_register(sbin, ext, &ereg); if ( !sieve_binary_read_unsigned(sblock, &offset, &version) || !sieve_binary_read_unsigned(sblock, &offset, &ereg->block_id) ) { result = -1; } else if ( !sieve_extension_version_is(ext, version) ) { sieve_sys_debug(sbin->svinst, "binary open: binary %s was compiled with different version " "of the `%s' extension (compiled v%d, expected v%d;" "automatically fixed when re-compiled)", sbin->path, sieve_extension_name(ext), version, sieve_extension_version(ext)); result = 0; } } } else result = -1; } T_END; } return result; }
static inline bool _save_skip_aligned (struct sieve_binary *sbin, struct ostream *stream, size_t size, uoff_t *offset) { uoff_t aligned_offset = SIEVE_BINARY_ALIGN(stream->offset); if ( (o_stream_seek(stream, aligned_offset + size)) <= 0 ) { sieve_sys_error(sbin->svinst, "binary save: failed to skip output stream " "to position %"PRIuUOFF_T": %s", aligned_offset + size, strerror(stream->stream_errno)); return FALSE; } if ( offset != NULL ) *offset = aligned_offset; return TRUE; }
/* FIXME: Is this even necessary for a file? */ static bool _save_full (struct sieve_binary *sbin, struct ostream *stream, const void *data, size_t size) { size_t bytes_left = size; const void *pdata = data; while ( bytes_left > 0 ) { ssize_t ret; if ( (ret=o_stream_send(stream, pdata, bytes_left)) <= 0 ) { sieve_sys_error(sbin->svinst, "binary save: failed to write %"PRIuSIZE_T" bytes " "to output stream: %s", bytes_left, strerror(stream->stream_errno)); return FALSE; } pdata = PTR_OFFSET(pdata, ret); bytes_left -= ret; } return TRUE; }
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 int cmd_test_binary_operation_execute (const struct sieve_runtime_env *renv, sieve_size_t *address) { const struct sieve_operation *oprtn = renv->oprtn; string_t *binary_name = NULL; int ret; /* * Read operands */ /* Binary Name */ if ( (ret=sieve_opr_string_read(renv, address, "binary-name", &binary_name)) <= 0 ) return ret; /* * Perform operation */ if ( sieve_operation_is(oprtn, test_binary_load_operation) ) { struct sieve_binary *sbin = testsuite_binary_load(str_c(binary_name)); if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) { sieve_runtime_trace(renv, 0, "testsuite: test_binary_load command"); sieve_runtime_trace_descend(renv); sieve_runtime_trace(renv, 0, "load binary `%s'", str_c(binary_name)); } if ( sbin != NULL ) { testsuite_script_set_binary(sbin); sieve_binary_unref(&sbin); } else { sieve_sys_error(testsuite_sieve_instance, "failed to load binary %s", str_c(binary_name)); return SIEVE_EXEC_FAILURE; } } else if ( sieve_operation_is(oprtn, test_binary_save_operation) ) { struct sieve_binary *sbin = testsuite_script_get_binary(); if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) { sieve_runtime_trace(renv, 0, "testsuite: test_binary_save command"); sieve_runtime_trace_descend(renv); sieve_runtime_trace(renv, 0, "save binary `%s'", str_c(binary_name)); } if ( sbin != NULL ) testsuite_binary_save(sbin, str_c(binary_name)); else { sieve_sys_error(testsuite_sieve_instance, "no compiled binary to save as %s", str_c(binary_name)); return SIEVE_EXEC_FAILURE; } } else { i_unreached(); } return SIEVE_EXEC_OK; }
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; }
int sieve_binary_save (struct sieve_binary *sbin, const char *path, bool update, mode_t save_mode, enum sieve_error *error_r) { int result, fd; string_t *temp_path; struct ostream *stream; if ( error_r != NULL ) *error_r = SIEVE_ERROR_NONE; /* Check whether saving is necessary */ if ( !update && sbin->path != NULL && strcmp(sbin->path, path) == 0 ) { if ( sbin->svinst->debug ) { sieve_sys_debug(sbin->svinst, "binary save: not saving binary %s, " "because it is already stored", path); } return 0; } /* Open it as temp file first, as not to overwrite an existing just yet */ temp_path = t_str_new(256); str_append(temp_path, path); str_append_c(temp_path, '.'); fd = safe_mkstemp_hostpid(temp_path, save_mode, (uid_t)-1, (gid_t)-1); if ( fd < 0 ) { if ( errno == EACCES ) { sieve_sys_error(sbin->svinst, "binary save: failed to create temporary file: %s", eacces_error_get_creating("open", str_c(temp_path))); if ( error_r != NULL ) *error_r = SIEVE_ERROR_NO_PERMISSION; } else { sieve_sys_error(sbin->svinst, "binary save: failed to create temporary file: open(%s) failed: %m", str_c(temp_path)); if ( error_r != NULL ) *error_r = SIEVE_ERROR_TEMP_FAILURE; } return -1; } /* Save binary */ result = 1; stream = o_stream_create_fd(fd, 0, FALSE); if ( !_sieve_binary_save(sbin, stream) ) { result = -1; if ( error_r != NULL ) *error_r = SIEVE_ERROR_TEMP_FAILURE; } o_stream_destroy(&stream); /* Close saved binary */ if ( close(fd) < 0 ) { sieve_sys_error(sbin->svinst, "binary save: failed to close temporary file: " "close(fd=%s) failed: %m", str_c(temp_path)); } /* Replace any original binary atomically */ if ( result && (rename(str_c(temp_path), path) < 0) ) { if ( errno == EACCES ) { sieve_sys_error(sbin->svinst, "binary save: failed to save binary: %s", eacces_error_get_creating("rename", path)); if ( error_r != NULL ) *error_r = SIEVE_ERROR_NO_PERMISSION; } else { sieve_sys_error(sbin->svinst, "binary save: failed to save binary: " "rename(%s, %s) failed: %m", str_c(temp_path), path); if ( error_r != NULL ) *error_r = SIEVE_ERROR_TEMP_FAILURE; } result = -1; } if ( result < 0 ) { /* Get rid of temp output (if any) */ if ( unlink(str_c(temp_path)) < 0 && errno != ENOENT ) { sieve_sys_error(sbin->svinst, "binary save: failed to clean up after error: unlink(%s) failed: %m", str_c(temp_path)); } } else { if ( sbin->path == NULL ) { sbin->path = p_strdup(sbin->pool, path); } } return result; }
bool sieve_binary_file_open (struct sieve_binary_file *file, struct sieve_instance *svinst, const char *path, enum sieve_error *error_r) { int fd; bool result = TRUE; struct stat st; if ( error_r != NULL ) *error_r = SIEVE_ERROR_NONE; if ( (fd=open(path, O_RDONLY)) < 0 ) { switch ( errno ) { case ENOENT: if ( error_r != NULL ) *error_r = SIEVE_ERROR_NOT_FOUND; break; case EACCES: sieve_sys_error(svinst, "binary open: failed to open: %s", eacces_error_get("open", path)); if ( error_r != NULL ) *error_r = SIEVE_ERROR_NO_PERMISSION; break; default: sieve_sys_error(svinst, "binary open: failed to open: " "open(%s) failed: %m", path); if ( error_r != NULL ) *error_r = SIEVE_ERROR_TEMP_FAILURE; break; } return FALSE; } if ( fstat(fd, &st) < 0 ) { if ( errno != ENOENT ) { sieve_sys_error(svinst, "binary open: fstat(fd=%s) failed: %m", path); } result = FALSE; } if ( result && !S_ISREG(st.st_mode) ) { sieve_sys_error(svinst, "binary open: %s is not a regular file", path); result = FALSE; } if ( !result ) { if ( close(fd) < 0 ) { sieve_sys_error(svinst, "binary open: close(fd=%s) failed after error: %m", path); } return FALSE; } file->svinst = svinst; file->fd = fd; file->st = st; return TRUE; }
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; }