int sieve_file_storage_save_continue (struct sieve_storage_save_context *sctx) { struct sieve_file_save_context *fsctx = (struct sieve_file_save_context *)sctx; switch (o_stream_send_istream(fsctx->output, sctx->input)) { case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: return 0; case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: i_unreached(); case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: sieve_storage_set_critical(sctx->storage, "save: read(%s) failed: %s", i_stream_get_name(sctx->input), i_stream_get_error(sctx->input)); return -1; case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: sieve_storage_set_critical(sctx->storage, "save: write(%s) failed: %s", fsctx->tmp_path, o_stream_get_error(fsctx->output)); return -1; } return 0; }
static int sieve_file_storage_create_tmp (struct sieve_file_storage *fstorage, const char *scriptname, const char **fpath_r) { struct sieve_storage *storage = &fstorage->storage; struct stat st; unsigned int prefix_len; const char *tmp_fname = NULL; string_t *path; int fd; path = t_str_new(256); str_append(path, fstorage->path); str_append(path, "/tmp/"); prefix_len = str_len(path); for (;;) { tmp_fname = sieve_generate_tmp_filename(scriptname); str_truncate(path, prefix_len); str_append(path, tmp_fname); /* stat() first to see if it exists. pretty much the only possibility of that happening is if time had moved backwards, but even then it's highly unlikely. */ if (stat(str_c(path), &st) == 0) { /* try another file name */ } else if (errno != ENOENT) { sieve_storage_set_critical(storage, "save: " "stat(%s) failed: %m", str_c(path)); return -1; } else { /* doesn't exist */ mode_t old_mask = umask(0777 & ~(fstorage->file_create_mode)); fd = open(str_c(path), O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0777); umask(old_mask); if (fd != -1 || errno != EEXIST) break; /* race condition between stat() and open(). highly unlikely. */ } } *fpath_r = str_c(path); if (fd == -1) { if (ENOQUOTA(errno)) { sieve_storage_set_error(storage, SIEVE_ERROR_NO_QUOTA, "Not enough disk quota"); } else { sieve_storage_set_critical(storage, "save: " "open(%s) failed: %m", str_c(path)); } } return fd; }
static int sieve_dict_storage_init (struct sieve_storage *storage, const char *const *options, enum sieve_error *error_r) { struct sieve_dict_storage *dstorage = (struct sieve_dict_storage *)storage; struct sieve_instance *svinst = storage->svinst; const char *uri = storage->location, *username = NULL; if ( options != NULL ) { while ( *options != NULL ) { const char *option = *options; if ( strncasecmp(option, "user="******"Invalid option `%s'", option); *error_r = SIEVE_ERROR_TEMP_FAILURE; return -1; } options++; } } if ( username == NULL ) { if ( svinst->username == NULL ) { sieve_storage_set_critical(storage, "No username specified"); *error_r = SIEVE_ERROR_TEMP_FAILURE; return -1; } username = svinst->username; } if ( svinst->base_dir == NULL ) { sieve_storage_set_critical(storage, "BUG: Sieve interpreter is initialized without a base_dir"); *error_r = SIEVE_ERROR_TEMP_FAILURE; return -1; } sieve_storage_sys_debug(storage, "user=%s, uri=%s", username, uri); dstorage->uri = p_strdup(storage->pool, uri); dstorage->username = p_strdup(storage->pool, username); storage->location = p_strconcat(storage->pool, SIEVE_DICT_STORAGE_DRIVER_NAME, ":", storage->location, ";user=", username, NULL); return 0; }
int sieve_file_storage_save_finish (struct sieve_storage_save_context *sctx) { struct sieve_file_save_context *fsctx = (struct sieve_file_save_context *)sctx; struct sieve_storage *storage = sctx->storage; int output_errno; if ( sctx->failed && fsctx->fd == -1 ) { /* tmp file creation failed */ return -1; } T_BEGIN { output_errno = fsctx->output->stream_errno; o_stream_destroy(&fsctx->output); if ( fsync(fsctx->fd) < 0 ) { sieve_storage_set_critical(storage, "save: " "fsync(%s) failed: %m", fsctx->tmp_path); sctx->failed = TRUE; } if ( close(fsctx->fd) < 0 ) { sieve_storage_set_critical(storage, "save: " "close(%s) failed: %m", fsctx->tmp_path); sctx->failed = TRUE; } fsctx->fd = -1; if ( sctx->failed ) { /* delete the tmp file */ if (unlink(fsctx->tmp_path) < 0 && errno != ENOENT) { sieve_storage_sys_warning(storage, "save: " "unlink(%s) failed: %m", fsctx->tmp_path); } fsctx->tmp_path = NULL; errno = output_errno; if ( ENOQUOTA(errno) ) { sieve_storage_set_error(storage, SIEVE_ERROR_NO_QUOTA, "Not enough disk quota"); } else if ( errno != 0 ) { sieve_storage_set_critical(storage, "save: " "write(%s) failed: %m", fsctx->tmp_path); } } } T_END; return ( sctx->failed ? -1 : 0 ); }
int sieve_dict_storage_get_dict (struct sieve_dict_storage *dstorage, struct dict **dict_r, enum sieve_error *error_r) { struct sieve_storage *storage = &dstorage->storage; struct sieve_instance *svinst = storage->svinst; struct dict_settings dict_set; const char *error; int ret; if ( dstorage->dict == NULL ) { memset(&dict_set, 0, sizeof(dict_set)); dict_set.username = dstorage->username; dict_set.base_dir = svinst->base_dir; ret = dict_init(dstorage->uri, &dict_set, &dstorage->dict, &error); if ( ret < 0 ) { sieve_storage_set_critical(storage, "Failed to initialize dict with data `%s' for user `%s': %s", dstorage->uri, dstorage->username, error); *error_r = SIEVE_ERROR_TEMP_FAILURE; return -1; } } *dict_r = dstorage->dict; return 0; }
static int sieve_ldap_script_open (struct sieve_script *script, enum sieve_error *error_r) { struct sieve_ldap_script *lscript = (struct sieve_ldap_script *)script; struct sieve_storage *storage = script->storage; struct sieve_ldap_storage *lstorage = (struct sieve_ldap_storage *)storage; int ret; if ( sieve_ldap_db_connect(lstorage->conn) < 0 ) { sieve_storage_set_critical(storage, "Failed to connect to LDAP database"); *error_r = storage->error_code; return -1; } if ( (ret=sieve_ldap_db_lookup_script( lstorage->conn, script->name, &lscript->dn, &lscript->modattr)) <= 0 ) { if ( ret == 0 ) { sieve_script_sys_debug(script, "Script not found"); sieve_script_set_error(script, SIEVE_ERROR_NOT_FOUND, "Sieve script not found"); } else { sieve_script_set_internal_error(script); } *error_r = script->storage->error_code; return -1; } return 0; }
static int sieve_file_storage_script_move (struct sieve_file_save_context *fsctx, const char *dst) { struct sieve_storage_save_context *sctx = &fsctx->context; struct sieve_storage *storage = sctx->storage; int result = 0; T_BEGIN { /* Using rename() to ensure existing files are replaced * without conflicts with other processes using the same * file. The kernel wont fully delete the original until * all processes have closed the file. */ if (rename(fsctx->tmp_path, dst) == 0) result = 0; else { result = -1; if ( ENOQUOTA(errno) ) { sieve_storage_set_error(storage, SIEVE_ERROR_NO_QUOTA, "Not enough disk quota"); } else if ( errno == EACCES ) { sieve_storage_set_critical(storage, "save: " "Failed to save Sieve script: " "%s", eacces_error_get("rename", dst)); } else { sieve_storage_set_critical(storage, "save: " "rename(%s, %s) failed: %m", fsctx->tmp_path, dst); } } /* Always destroy temp file */ if (unlink(fsctx->tmp_path) < 0 && errno != ENOENT) { sieve_storage_sys_warning(storage, "save: " "unlink(%s) failed: %m", fsctx->tmp_path); } } T_END; return result; }
bool cmd_getscript(struct client_command_context *cmd) { struct client *client = cmd->client; struct cmd_getscript_context *ctx; const char *scriptname; enum sieve_error error; /* <scriptname> */ if ( !client_read_string_args(cmd, TRUE, 1, &scriptname) ) return FALSE; ctx = p_new(cmd->pool, struct cmd_getscript_context, 1); ctx->cmd = cmd; ctx->client = client; ctx->storage = client->storage; ctx->failed = FALSE; ctx->script = sieve_storage_open_script (client->storage, scriptname, NULL); if (ctx->script == NULL) { ctx->failed = TRUE; return cmd_getscript_finish(ctx); } if ( sieve_script_get_stream (ctx->script, &ctx->script_stream, &error) < 0 ) { if ( error == SIEVE_ERROR_NOT_FOUND ) sieve_storage_set_error(client->storage, error, "Script does not exist."); ctx->failed = TRUE; return cmd_getscript_finish(ctx); } if ( sieve_script_get_size(ctx->script, &ctx->script_size) <= 0 ) { sieve_storage_set_critical(ctx->storage, "failed to obtain script size for script `%s' from %s", sieve_script_name(ctx->script), sieve_script_location(ctx->script)); ctx->failed = TRUE; return cmd_getscript_finish(ctx); } i_assert(ctx->script_stream->v_offset == 0); client_send_line (client, t_strdup_printf("{%"PRIuUOFF_T"}", ctx->script_size)); client->command_pending = TRUE; cmd->func = cmd_getscript_continue; cmd->context = ctx; return cmd_getscript_continue(cmd); }
static bool cmd_getscript_continue(struct client_command_context *cmd) { struct client *client = cmd->client; struct cmd_getscript_context *ctx = cmd->context; switch (o_stream_send_istream(client->output, ctx->script_stream)) { case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: if ( ctx->script_stream->v_offset != ctx->script_size && !ctx->failed ) { /* Input stream gave less data than expected */ sieve_storage_set_critical(ctx->storage, "GETSCRIPT for script `%s' from %s got too little data: " "%"PRIuUOFF_T" vs %"PRIuUOFF_T, sieve_script_name(ctx->script), sieve_script_location(ctx->script), ctx->script_stream->v_offset, ctx->script_size); client_disconnect(ctx->client, "GETSCRIPT failed"); ctx->failed = TRUE; } break; case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: i_unreached(); case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: return FALSE; case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: sieve_storage_set_critical(ctx->storage, "o_stream_send_istream() failed for script `%s' from %s: %s", sieve_script_name(ctx->script), sieve_script_location(ctx->script), i_stream_get_error(ctx->script_stream)); ctx->failed = TRUE; break; case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: client_disconnect(ctx->client, io_stream_get_disconnect_reason(client->input, client->output)); ctx->failed = TRUE; break; } return cmd_getscript_finish(ctx); }
struct sieve_script *sieve_file_storage_save_get_tempscript (struct sieve_storage_save_context *sctx) { struct sieve_file_save_context *fsctx = (struct sieve_file_save_context *)sctx; struct sieve_file_storage *fstorage = (struct sieve_file_storage *)sctx->storage; struct sieve_file_script *tmpscript; enum sieve_error error; const char *scriptname; if (sctx->failed) return NULL; if ( sctx->scriptobject != NULL ) return sctx->scriptobject; scriptname = ( sctx->scriptname == NULL ? "" : sctx->scriptname ); tmpscript = sieve_file_script_open_from_path (fstorage, fsctx->tmp_path, scriptname, &error); if ( tmpscript == NULL ) { if ( error == SIEVE_ERROR_NOT_FOUND ) { sieve_storage_set_critical(sctx->storage, "save: " "Temporary script file `%s' got lost, " "which should not happen (possibly deleted externally).", fsctx->tmp_path); } else { sieve_storage_set_critical(sctx->storage, "save: " "Failed to open temporary script file `%s'", fsctx->tmp_path); } return NULL; } return &tmpscript->script; }
static bool cmd_getscript_continue(struct client_command_context *cmd) { struct client *client = cmd->client; struct cmd_getscript_context *ctx = cmd->context; off_t ret; ret = o_stream_send_istream(client->output, ctx->script_stream); if ( ret < 0 ) { sieve_storage_set_critical(ctx->storage, "o_stream_send_istream(%s) failed: %m", sieve_script_filename(ctx->script)); ctx->failed = TRUE; return cmd_getscript_finish(ctx); } ctx->script_offset += ret; if ( ctx->script_offset != ctx->script_size && !ctx->failed ) { /* unfinished */ if ( !i_stream_have_bytes_left(ctx->script_stream) ) { /* Input stream gave less data than expected */ sieve_storage_set_critical(ctx->storage, "GETSCRIPT for SCRIPT %s got too little data: " "%"PRIuUOFF_T" vs %"PRIuUOFF_T, sieve_script_name(ctx->script), ctx->script_offset, ctx->script_size); client_disconnect(ctx->client, "GETSCRIPT failed"); ctx->failed = TRUE; return cmd_getscript_finish(ctx); } return FALSE; } return cmd_getscript_finish(ctx); }
int sieve_storage_quota_havespace (struct sieve_storage *storage, const char *scriptname, size_t size, enum sieve_storage_quota *quota_r, uint64_t *limit_r) { struct dirent *dp; DIR *dirp; uint64_t script_count = 1; uint64_t script_storage = size; int result = 1; *limit_r = 0; *quota_r = SIEVE_STORAGE_QUOTA_NONE; /* Check the script size */ if ( !sieve_storage_quota_validsize(storage, size, limit_r) ) { *quota_r = SIEVE_STORAGE_QUOTA_MAXSIZE; return 0; } /* Do we need to scan the storage (quota enabled) ? */ if ( storage->max_scripts == 0 && storage->max_storage == 0 ) { return 1; } /* Open the directory */ if ( (dirp = opendir(storage->dir)) == NULL ) { sieve_storage_set_critical (storage, "quota: opendir(%s) failed: %m", storage->dir); return -1; } /* Scan all files */ for (;;) { const char *name; bool replaced = FALSE; /* Read next entry */ errno = 0; if ( (dp = readdir(dirp)) == NULL ) { if ( errno != 0 ) { sieve_storage_set_critical (storage, "quota: readdir(%s) failed: %m", storage->dir); result = -1; } break; } /* Parse filename */ name = sieve_storage_file_get_scriptname(storage, dp->d_name); /* Ignore non-script files */ if ( name == NULL ) continue; /* Don't list our active sieve script link if the link * resides in the script dir (generally a bad idea). */ if ( *(storage->link_path) == '\0' && strcmp(storage->active_fname, dp->d_name) == 0 ) continue; if ( strcmp(name, scriptname) == 0 ) replaced = TRUE; /* Check cont quota if necessary */ if ( storage->max_scripts > 0 ) { if ( !replaced ) { script_count++; if ( script_count > storage->max_scripts ) { *quota_r = SIEVE_STORAGE_QUOTA_MAXSCRIPTS; *limit_r = storage->max_scripts; result = 0; break; } } } /* Check storage quota if necessary */ if ( storage->max_storage > 0 ) { const char *path; struct stat st; int ret; path = t_strconcat(storage->dir, "/", dp->d_name, NULL); if ( (ret=stat(path, &st)) < 0 ) { i_warning ("sieve-storage: quota: stat(%s) failed: %m", path); continue; } if ( !replaced ) { script_storage += st.st_size; if ( script_storage > storage->max_storage ) { *quota_r = SIEVE_STORAGE_QUOTA_MAXSTORAGE; *limit_r = storage->max_storage; result = 0; break; } } } } /* Close directory */ if ( closedir(dirp) < 0 ) { sieve_storage_set_critical (storage, "quota: closedir(%s) failed: %m", storage->dir); } return result; }
int sieve_ldap_storage_read_settings (struct sieve_ldap_storage *lstorage, const char *config_path) { struct sieve_storage *storage = &lstorage->storage; const char *str, *error; struct stat st; if ( stat(config_path, &st) < 0 ) { sieve_storage_sys_error(storage, "Failed to read LDAP storage config: " "stat(%s) failed: %m", config_path); return -1; } lstorage->set = default_settings; lstorage->set_mtime = st.st_mtime; if (!settings_read_nosection (config_path, parse_setting, lstorage, &error)) { sieve_storage_set_critical(storage, "Failed to read LDAP storage config `%s': %s", config_path, error); return -1; } if (lstorage->set.base == NULL) { sieve_storage_set_critical(storage, "Invalid LDAP storage config `%s': " "No search base given", config_path); return -1; } if (lstorage->set.uris == NULL && lstorage->set.hosts == NULL) { sieve_storage_set_critical(storage, "Invalid LDAP storage config `%s': " "No uris or hosts set", config_path); return -1; } if (*lstorage->set.ldaprc_path != '\0') { str = getenv("LDAPRC"); if (str != NULL && strcmp(str, lstorage->set.ldaprc_path) != 0) { sieve_storage_set_critical(storage, "Invalid LDAP storage config `%s': " "Multiple different ldaprc_path settings not allowed " "(%s and %s)", config_path, str, lstorage->set.ldaprc_path); return -1; } env_put(t_strconcat("LDAPRC=", lstorage->set.ldaprc_path, NULL)); } if ( ldap_deref_from_str (lstorage->set.deref, &lstorage->set.ldap_deref) < 0 ) { sieve_storage_set_critical(storage, "Invalid LDAP storage config `%s': " "Invalid deref option `%s'", config_path, lstorage->set.deref);; } if ( ldap_scope_from_str (lstorage->set.scope, &lstorage->set.ldap_scope) < 0 ) { sieve_storage_set_critical(storage, "Invalid LDAP storage config `%s': " "Invalid scope option `%s'", config_path, lstorage->set.scope);; } #ifdef OPENLDAP_TLS_OPTIONS if ( lstorage->set.tls_require_cert != NULL && ldap_tls_require_cert_from_str(lstorage->set.tls_require_cert, &lstorage->set.ldap_tls_require_cert) < 0) { sieve_storage_set_critical(storage, "Invalid LDAP storage config `%s': " "Invalid tls_require_cert option `%s'", config_path, lstorage->set.tls_require_cert); } #endif return 0; }
static int sieve_file_script_sequence_read_dir (struct sieve_file_script_sequence *fseq, const char *path) { struct sieve_storage *storage = fseq->seq.storage; DIR *dirp; int ret = 0; /* Open the directory */ if ( (dirp = opendir(path)) == NULL ) { switch ( errno ) { case ENOENT: sieve_storage_set_error(storage, SIEVE_ERROR_NOT_FOUND, "Script sequence location not found"); break; case EACCES: sieve_storage_set_error(storage, SIEVE_ERROR_NO_PERMISSION, "Script sequence location not accessible"); sieve_storage_sys_error(storage, "Failed to open sieve sequence: " "%s", eacces_error_get("stat", path)); break; default: sieve_storage_set_critical(storage, "Failed to open sieve sequence: " "opendir(%s) failed: %m", path); break; } return -1; } /* Read and sort script files */ for (;;) { const char *const *files; unsigned int count, i; const char *file; struct dirent *dp; struct stat st; errno = 0; if ( (dp=readdir(dirp)) == NULL ) break; if ( !sieve_script_file_has_extension(dp->d_name) ) continue; file = NULL; T_BEGIN { if ( path[strlen(path)-1] == '/' ) file = t_strconcat(path, dp->d_name, NULL); else file = t_strconcat(path, "/", dp->d_name, NULL); if ( stat(file, &st) == 0 && S_ISREG(st.st_mode) ) file = p_strdup(fseq->pool, dp->d_name); else file = NULL; } T_END; if (file == NULL) continue; /* Insert into sorted array */ files = array_get(&fseq->script_files, &count); for ( i = 0; i < count; i++ ) { if ( strcmp(file, files[i]) < 0 ) break; } if ( i == count ) array_append(&fseq->script_files, &file, 1); else array_insert(&fseq->script_files, i, &file, 1); } if ( errno != 0 ) { sieve_storage_set_critical(storage, "Failed to read sequence directory: " "readdir(%s) failed: %m", path); ret = -1; } /* Close the directory */ if ( dirp != NULL && closedir(dirp) < 0 ) { sieve_storage_sys_error(storage, "Failed to close sequence directory: " "closedir(%s) failed: %m", path); } return ret; }
struct sieve_script_sequence *sieve_file_storage_get_script_sequence (struct sieve_storage *storage, enum sieve_error *error_r) { struct sieve_file_storage *fstorage = (struct sieve_file_storage *)storage; struct sieve_file_script_sequence *fseq = NULL; const char *name = storage->script_name; const char *file; pool_t pool; struct stat st; /* Specified path can either be a regular file or a directory */ if ( stat(fstorage->path, &st) != 0 ) { switch ( errno ) { case ENOENT: sieve_storage_set_error(storage, SIEVE_ERROR_NOT_FOUND, "Script sequence location not found"); break; case EACCES: sieve_storage_set_error(storage, SIEVE_ERROR_NO_PERMISSION, "Script sequence location not accessible"); sieve_storage_sys_error(storage, "Failed to open sieve sequence: " "%s", eacces_error_get("stat", fstorage->path)); break; default: sieve_storage_set_critical(storage, "Failed to open sieve sequence: " "stat(%s) failed: %m", fstorage->path); break; } *error_r = storage->error_code; return NULL; } /* Create sequence object */ pool = pool_alloconly_create("sieve_file_script_sequence", 1024); fseq = p_new(pool, struct sieve_file_script_sequence, 1); fseq->pool = pool; sieve_script_sequence_init(&fseq->seq, storage); if ( S_ISDIR(st.st_mode) ) { i_array_init(&fseq->script_files, 16); /* Path is directory */ if (name == 0 || *name == '\0') { /* Read all '.sieve' files in directory */ if (sieve_file_script_sequence_read_dir (fseq, fstorage->path) < 0) { *error_r = storage->error_code; sieve_file_script_sequence_destroy(&fseq->seq); return NULL; } } else { /* Read specific script file */ file = sieve_script_file_from_name(name); file = p_strdup(pool, file); array_append(&fseq->script_files, &file, 1); } } else { /* Path is a file (apparently; we'll see about that once it is opened) */ fseq->storage_is_file = TRUE; } return &fseq->seq; }
static int sieve_file_storage_save_to(struct sieve_file_storage *fstorage, string_t *temp_path, struct istream *input, const char *target) { struct sieve_storage *storage = &fstorage->storage; struct ostream *output; int fd; // FIXME: move this to base class // FIXME: use io_stream_temp fd = safe_mkstemp_hostpid (temp_path, fstorage->file_create_mode, (uid_t)-1, (gid_t)-1); if ( fd < 0 ) { if ( errno == EACCES ) { sieve_storage_set_critical(storage, "Failed to create temporary file: %s", eacces_error_get_creating("open", str_c(temp_path))); } else { sieve_storage_set_critical(storage, "Failed to create temporary file: open(%s) failed: %m", str_c(temp_path)); } return -1; } output = o_stream_create_fd(fd, 0); switch ( o_stream_send_istream(output, input) ) { case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: break; case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: i_unreached(); case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: sieve_storage_set_critical(storage, "read(%s) failed: %s", i_stream_get_name(input), i_stream_get_error(input)); o_stream_destroy(&output); i_unlink(str_c(temp_path)); return -1; case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: sieve_storage_set_critical(storage, "write(%s) failed: %s", str_c(temp_path), o_stream_get_error(output)); o_stream_destroy(&output); i_unlink(str_c(temp_path)); return -1; } o_stream_destroy(&output); if ( rename(str_c(temp_path), target) < 0 ) { if ( ENOQUOTA(errno) ) { sieve_storage_set_error(storage, SIEVE_ERROR_NO_QUOTA, "Not enough disk quota"); } else if ( errno == EACCES ) { sieve_storage_set_critical(storage, "%s", eacces_error_get("rename", target)); } else { sieve_storage_set_critical(storage, "rename(%s, %s) failed: %m", str_c(temp_path), target); } i_unlink(str_c(temp_path)); } return 0; }